veryfront 0.0.75 → 0.0.79
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/README.md +33 -150
- package/bin/veryfront.js +22 -6
- package/esm/_dnt.polyfills.d.ts +172 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +274 -0
- package/esm/_dnt.shims.d.ts +23 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +79 -0
- package/esm/deno.d.ts +325 -0
- package/esm/deno.d.ts.map +1 -0
- package/esm/deno.js +356 -0
- package/esm/deps/deno.land/std@0.220.0/assert/assert.d.ts +13 -0
- package/esm/deps/deno.land/std@0.220.0/assert/assert.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/assert/assert.js +18 -0
- package/esm/deps/deno.land/std@0.220.0/assert/assertion_error.d.ts +15 -0
- package/esm/deps/deno.land/std@0.220.0/assert/assertion_error.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/assert/assertion_error.js +18 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_create_walk_entry.d.ts +14 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_create_walk_entry.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_create_walk_entry.js +34 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_get_file_info_type.d.ts +10 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_get_file_info_type.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_get_file_info_type.js +15 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_is_same_path.d.ts +7 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_is_same_path.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_is_same_path.js +14 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_is_subdir.d.ts +8 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_is_subdir.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_is_subdir.js +20 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_to_path_string.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_to_path_string.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/_to_path_string.js +10 -0
- package/esm/deps/deno.land/std@0.220.0/fs/copy.d.ts +52 -0
- package/esm/deps/deno.land/std@0.220.0/fs/copy.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/copy.js +248 -0
- package/esm/deps/deno.land/std@0.220.0/fs/empty_dir.d.ts +31 -0
- package/esm/deps/deno.land/std@0.220.0/fs/empty_dir.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/empty_dir.js +70 -0
- package/esm/deps/deno.land/std@0.220.0/fs/ensure_dir.d.ts +27 -0
- package/esm/deps/deno.land/std@0.220.0/fs/ensure_dir.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/ensure_dir.js +83 -0
- package/esm/deps/deno.land/std@0.220.0/fs/ensure_file.d.ts +33 -0
- package/esm/deps/deno.land/std@0.220.0/fs/ensure_file.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/ensure_file.js +76 -0
- package/esm/deps/deno.land/std@0.220.0/fs/ensure_link.d.ts +31 -0
- package/esm/deps/deno.land/std@0.220.0/fs/ensure_link.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/ensure_link.js +43 -0
- package/esm/deps/deno.land/std@0.220.0/fs/ensure_symlink.d.ts +21 -0
- package/esm/deps/deno.land/std@0.220.0/fs/ensure_symlink.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/ensure_symlink.js +94 -0
- package/esm/deps/deno.land/std@0.220.0/fs/eol.d.ts +48 -0
- package/esm/deps/deno.land/std@0.220.0/fs/eol.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/eol.js +60 -0
- package/esm/deps/deno.land/std@0.220.0/fs/exists.d.ts +102 -0
- package/esm/deps/deno.land/std@0.220.0/fs/exists.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/exists.js +164 -0
- package/esm/deps/deno.land/std@0.220.0/fs/expand_glob.d.ts +58 -0
- package/esm/deps/deno.land/std@0.220.0/fs/expand_glob.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/expand_glob.js +226 -0
- package/esm/deps/deno.land/std@0.220.0/fs/mod.d.ts +17 -0
- package/esm/deps/deno.land/std@0.220.0/fs/mod.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/mod.js +17 -0
- package/esm/deps/deno.land/std@0.220.0/fs/move.d.ts +40 -0
- package/esm/deps/deno.land/std@0.220.0/fs/move.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/move.js +95 -0
- package/esm/deps/deno.land/std@0.220.0/fs/walk.d.ts +81 -0
- package/esm/deps/deno.land/std@0.220.0/fs/walk.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/fs/walk.js +160 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/assert_path.d.ts +2 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/assert_path.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/assert_path.js +7 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/basename.d.ts +4 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/basename.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/basename.js +40 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/common.d.ts +2 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/common.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/common.js +23 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/constants.d.ts +40 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/constants.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/constants.js +46 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/dirname.d.ts +2 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/dirname.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/dirname.js +8 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/format.d.ts +4 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/format.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/format.js +19 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/from_file_url.d.ts +2 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/from_file_url.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/from_file_url.js +9 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/glob_to_reg_exp.d.ts +29 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/glob_to_reg_exp.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/glob_to_reg_exp.js +237 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/normalize.d.ts +2 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/normalize.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/normalize.js +8 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/normalize_string.d.ts +2 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/normalize_string.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/normalize_string.js +77 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/relative.d.ts +2 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/relative.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/relative.js +9 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/strip_trailing_separators.d.ts +2 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/strip_trailing_separators.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/strip_trailing_separators.js +19 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/to_file_url.d.ts +2 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/to_file_url.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_common/to_file_url.js +15 -0
- package/esm/deps/deno.land/std@0.220.0/path/_interface.d.ts +27 -0
- package/esm/deps/deno.land/std@0.220.0/path/_interface.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_interface.js +3 -0
- package/esm/deps/deno.land/std@0.220.0/path/_os.d.ts +4 -0
- package/esm/deps/deno.land/std@0.220.0/path/_os.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/_os.js +18 -0
- package/esm/deps/deno.land/std@0.220.0/path/basename.d.ts +9 -0
- package/esm/deps/deno.land/std@0.220.0/path/basename.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/basename.js +17 -0
- package/esm/deps/deno.land/std@0.220.0/path/common.d.ts +14 -0
- package/esm/deps/deno.land/std@0.220.0/path/common.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/common.js +19 -0
- package/esm/deps/deno.land/std@0.220.0/path/constants.d.ts +4 -0
- package/esm/deps/deno.land/std@0.220.0/path/constants.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/constants.js +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/dirname.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/dirname.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/dirname.js +12 -0
- package/esm/deps/deno.land/std@0.220.0/path/extname.d.ts +7 -0
- package/esm/deps/deno.land/std@0.220.0/path/extname.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/extname.js +13 -0
- package/esm/deps/deno.land/std@0.220.0/path/format.d.ts +7 -0
- package/esm/deps/deno.land/std@0.220.0/path/format.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/format.js +12 -0
- package/esm/deps/deno.land/std@0.220.0/path/from_file_url.d.ts +18 -0
- package/esm/deps/deno.land/std@0.220.0/path/from_file_url.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/from_file_url.js +24 -0
- package/esm/deps/deno.land/std@0.220.0/path/glob_to_regexp.d.ts +63 -0
- package/esm/deps/deno.land/std@0.220.0/path/glob_to_regexp.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/glob_to_regexp.js +65 -0
- package/esm/deps/deno.land/std@0.220.0/path/is_absolute.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/is_absolute.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/is_absolute.js +12 -0
- package/esm/deps/deno.land/std@0.220.0/path/is_glob.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/path/is_glob.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/is_glob.js +28 -0
- package/esm/deps/deno.land/std@0.220.0/path/join.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/join.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/join.js +12 -0
- package/esm/deps/deno.land/std@0.220.0/path/join_globs.d.ts +5 -0
- package/esm/deps/deno.land/std@0.220.0/path/join_globs.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/join_globs.js +11 -0
- package/esm/deps/deno.land/std@0.220.0/path/mod.d.ts +57 -0
- package/esm/deps/deno.land/std@0.220.0/path/mod.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/mod.js +60 -0
- package/esm/deps/deno.land/std@0.220.0/path/normalize.d.ts +8 -0
- package/esm/deps/deno.land/std@0.220.0/path/normalize.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/normalize.js +14 -0
- package/esm/deps/deno.land/std@0.220.0/path/normalize_glob.d.ts +5 -0
- package/esm/deps/deno.land/std@0.220.0/path/normalize_glob.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/normalize_glob.js +11 -0
- package/esm/deps/deno.land/std@0.220.0/path/parse.d.ts +7 -0
- package/esm/deps/deno.land/std@0.220.0/path/parse.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/parse.js +12 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/_util.d.ts +2 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/_util.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/_util.js +8 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/basename.d.ts +18 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/basename.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/basename.js +27 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/common.d.ts +14 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/common.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/common.js +19 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/constants.d.ts +4 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/constants.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/constants.js +5 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/dirname.d.ts +15 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/dirname.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/dirname.js +45 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/extname.d.ts +16 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/extname.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/extname.js +68 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/format.d.ts +7 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/format.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/format.js +11 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/from_file_url.d.ts +12 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/from_file_url.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/from_file_url.js +17 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/glob_to_regexp.d.ts +58 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/glob_to_regexp.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/glob_to_regexp.js +69 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/is_absolute.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/is_absolute.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/is_absolute.js +12 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/is_glob.d.ts +2 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/is_glob.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/is_glob.js +3 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/join.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/join.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/join.js +26 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/join_globs.d.ts +5 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/join_globs.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/join_globs.js +26 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/mod.d.ts +39 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/mod.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/mod.js +42 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/normalize.d.ts +8 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/normalize.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/normalize.js +25 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/normalize_glob.d.ts +5 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/normalize_glob.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/normalize_glob.js +16 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/parse.d.ts +7 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/parse.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/parse.js +98 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/relative.d.ts +8 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/relative.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/relative.js +94 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/resolve.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/resolve.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/resolve.js +48 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/to_file_url.d.ts +12 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/to_file_url.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/to_file_url.js +22 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/to_namespaced_path.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/to_namespaced_path.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/posix/to_namespaced_path.js +10 -0
- package/esm/deps/deno.land/std@0.220.0/path/relative.d.ts +13 -0
- package/esm/deps/deno.land/std@0.220.0/path/relative.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/relative.js +19 -0
- package/esm/deps/deno.land/std@0.220.0/path/resolve.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/resolve.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/resolve.js +14 -0
- package/esm/deps/deno.land/std@0.220.0/path/to_file_url.d.ts +18 -0
- package/esm/deps/deno.land/std@0.220.0/path/to_file_url.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/to_file_url.js +24 -0
- package/esm/deps/deno.land/std@0.220.0/path/to_namespaced_path.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/to_namespaced_path.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/to_namespaced_path.js +14 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/_util.d.ts +4 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/_util.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/_util.js +15 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/basename.d.ts +9 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/basename.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/basename.js +30 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/common.d.ts +14 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/common.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/common.js +19 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/constants.d.ts +4 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/constants.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/constants.js +5 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/dirname.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/dirname.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/dirname.js +98 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/extname.d.ts +7 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/extname.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/extname.js +68 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/format.d.ts +7 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/format.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/format.js +11 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/from_file_url.d.ts +14 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/from_file_url.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/from_file_url.js +26 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/glob_to_regexp.d.ts +58 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/glob_to_regexp.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/glob_to_regexp.js +69 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/is_absolute.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/is_absolute.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/is_absolute.js +27 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/is_glob.d.ts +2 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/is_glob.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/is_glob.js +3 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/join.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/join.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/join.js +72 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/join_globs.d.ts +5 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/join_globs.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/join_globs.js +26 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/mod.d.ts +39 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/mod.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/mod.js +42 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/normalize.d.ts +8 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/normalize.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/normalize.js +130 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/normalize_glob.d.ts +5 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/normalize_glob.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/normalize_glob.js +16 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/parse.d.ts +7 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/parse.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/parse.js +159 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/relative.d.ts +13 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/relative.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/relative.js +121 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/resolve.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/resolve.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/resolve.js +143 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/to_file_url.d.ts +14 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/to_file_url.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/to_file_url.js +31 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/to_namespaced_path.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/to_namespaced_path.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/path/windows/to_namespaced_path.js +38 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_error.d.ts +7 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_error.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_error.js +15 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_loader/loader.d.ts +5 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_loader/loader.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_loader/loader.js +1343 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_loader/loader_state.d.ts +44 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_loader/loader_state.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_loader/loader_state.js +41 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_mark.d.ts +11 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_mark.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_mark.js +62 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_state.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_state.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_state.js +11 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/binary.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/binary.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/binary.js +107 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/bool.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/bool.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/bool.js +32 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/float.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/float.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/float.js +106 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/function.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/function.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/function.js +38 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/int.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/int.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/int.js +168 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/map.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/map.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/map.js +11 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/merge.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/merge.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/merge.js +12 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/mod.d.ts +17 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/mod.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/mod.js +20 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/nil.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/nil.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/nil.js +37 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/omap.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/omap.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/omap.js +40 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/pairs.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/pairs.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/pairs.js +34 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/regexp.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/regexp.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/regexp.js +37 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/seq.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/seq.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/seq.js +11 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/set.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/set.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/set.js +25 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/str.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/str.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/str.js +10 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/timestamp.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/timestamp.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/timestamp.js +78 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/undefined.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/undefined.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_type/undefined.js +20 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_utils.d.ts +20 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_utils.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/_utils.js +57 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/parse.d.ts +36 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/parse.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/parse.js +18 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/core.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/core.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/core.js +12 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/default.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/default.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/default.js +15 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/extended.d.ts +31 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/extended.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/extended.js +37 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/failsafe.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/failsafe.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/failsafe.js +12 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/json.d.ts +3 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/json.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/json.js +14 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/mod.d.ts +6 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/mod.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema/mod.js +10 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema.d.ts +23 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/schema.js +73 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/type.d.ts +29 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/type.d.ts.map +1 -0
- package/esm/deps/deno.land/std@0.220.0/yaml/type.js +33 -0
- package/esm/package.json +3 -0
- package/esm/src/agent/ai-defaults.d.ts +107 -0
- package/esm/src/agent/ai-defaults.d.ts.map +1 -0
- package/esm/src/agent/ai-defaults.js +63 -0
- package/esm/src/agent/composition/composition.d.ts +41 -0
- package/esm/src/agent/composition/composition.d.ts.map +1 -0
- package/esm/src/agent/composition/composition.js +112 -0
- package/esm/src/agent/composition/index.d.ts +3 -0
- package/esm/src/agent/composition/index.d.ts.map +1 -0
- package/esm/src/agent/composition/index.js +2 -0
- package/esm/src/agent/debug/index.d.ts +3 -0
- package/esm/src/agent/debug/index.d.ts.map +1 -0
- package/esm/src/agent/debug/index.js +2 -0
- package/esm/src/agent/debug/inspector.d.ts +88 -0
- package/esm/src/agent/debug/inspector.d.ts.map +1 -0
- package/esm/src/agent/debug/inspector.js +142 -0
- package/esm/src/agent/factory.d.ts +11 -0
- package/esm/src/agent/factory.d.ts.map +1 -0
- package/esm/src/agent/factory.js +109 -0
- package/esm/src/agent/index.d.ts +13 -0
- package/esm/src/agent/index.d.ts.map +1 -0
- package/esm/src/agent/index.js +11 -0
- package/esm/src/agent/memory/index.d.ts +5 -0
- package/esm/src/agent/memory/index.d.ts.map +1 -0
- package/esm/src/agent/memory/index.js +4 -0
- package/esm/src/agent/memory/memory-interface.d.ts +46 -0
- package/esm/src/agent/memory/memory-interface.d.ts.map +1 -0
- package/esm/src/agent/memory/memory-interface.js +23 -0
- package/esm/src/agent/memory/memory.d.ts +57 -0
- package/esm/src/agent/memory/memory.d.ts.map +1 -0
- package/esm/src/agent/memory/memory.js +178 -0
- package/esm/src/agent/memory/redis.d.ts +58 -0
- package/esm/src/agent/memory/redis.d.ts.map +1 -0
- package/esm/src/agent/memory/redis.js +114 -0
- package/esm/src/agent/middleware/cache/cache.d.ts +24 -0
- package/esm/src/agent/middleware/cache/cache.d.ts.map +1 -0
- package/esm/src/agent/middleware/cache/cache.js +216 -0
- package/esm/src/agent/middleware/cache/index.d.ts +7 -0
- package/esm/src/agent/middleware/cache/index.d.ts.map +1 -0
- package/esm/src/agent/middleware/cache/index.js +6 -0
- package/esm/src/agent/middleware/chain.d.ts +12 -0
- package/esm/src/agent/middleware/chain.d.ts.map +1 -0
- package/esm/src/agent/middleware/chain.js +38 -0
- package/esm/src/agent/middleware/cost-tracking/index.d.ts +2 -0
- package/esm/src/agent/middleware/cost-tracking/index.d.ts.map +1 -0
- package/esm/src/agent/middleware/cost-tracking/index.js +1 -0
- package/esm/src/agent/middleware/cost-tracking/tracker.d.ts +56 -0
- package/esm/src/agent/middleware/cost-tracking/tracker.d.ts.map +1 -0
- package/esm/src/agent/middleware/cost-tracking/tracker.js +156 -0
- package/esm/src/agent/middleware/index.d.ts +7 -0
- package/esm/src/agent/middleware/index.d.ts.map +1 -0
- package/esm/src/agent/middleware/index.js +6 -0
- package/esm/src/agent/middleware/rate-limit/index.d.ts +2 -0
- package/esm/src/agent/middleware/rate-limit/index.d.ts.map +1 -0
- package/esm/src/agent/middleware/rate-limit/index.js +1 -0
- package/esm/src/agent/middleware/rate-limit/limiter.d.ts +20 -0
- package/esm/src/agent/middleware/rate-limit/limiter.d.ts.map +1 -0
- package/esm/src/agent/middleware/rate-limit/limiter.js +141 -0
- package/esm/src/agent/middleware/security/index.d.ts +2 -0
- package/esm/src/agent/middleware/security/index.d.ts.map +1 -0
- package/esm/src/agent/middleware/security/index.js +1 -0
- package/esm/src/agent/middleware/security/validator.d.ts +92 -0
- package/esm/src/agent/middleware/security/validator.d.ts.map +1 -0
- package/esm/src/agent/middleware/security/validator.js +190 -0
- package/esm/src/agent/react/client.d.ts +12 -0
- package/esm/src/agent/react/client.d.ts.map +1 -0
- package/esm/src/agent/react/client.js +10 -0
- package/esm/src/agent/react/index.d.ts +19 -0
- package/esm/src/agent/react/index.d.ts.map +1 -0
- package/esm/src/agent/react/index.js +13 -0
- package/esm/src/agent/react/use-agent.d.ts +31 -0
- package/esm/src/agent/react/use-agent.d.ts.map +1 -0
- package/esm/src/agent/react/use-agent.js +72 -0
- package/esm/src/agent/react/use-chat/index.d.ts +9 -0
- package/esm/src/agent/react/use-chat/index.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/index.js +7 -0
- package/esm/src/agent/react/use-chat/streaming/handler.d.ts +3 -0
- package/esm/src/agent/react/use-chat/streaming/handler.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/streaming/handler.js +250 -0
- package/esm/src/agent/react/use-chat/streaming/index.d.ts +6 -0
- package/esm/src/agent/react/use-chat/streaming/index.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/streaming/index.js +4 -0
- package/esm/src/agent/react/use-chat/streaming/parts-builder.d.ts +4 -0
- package/esm/src/agent/react/use-chat/streaming/parts-builder.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/streaming/parts-builder.js +52 -0
- package/esm/src/agent/react/use-chat/streaming/types.d.ts +35 -0
- package/esm/src/agent/react/use-chat/streaming/types.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/streaming/types.js +1 -0
- package/esm/src/agent/react/use-chat/types.d.ts +89 -0
- package/esm/src/agent/react/use-chat/types.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/types.js +1 -0
- package/esm/src/agent/react/use-chat/use-chat.d.ts +6 -0
- package/esm/src/agent/react/use-chat/use-chat.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/use-chat.js +191 -0
- package/esm/src/agent/react/use-chat/utils.d.ts +4 -0
- package/esm/src/agent/react/use-chat/utils.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/utils.js +10 -0
- package/esm/src/agent/react/use-completion.d.ts +39 -0
- package/esm/src/agent/react/use-completion.d.ts.map +1 -0
- package/esm/src/agent/react/use-completion.js +80 -0
- package/esm/src/agent/react/use-streaming.d.ts +26 -0
- package/esm/src/agent/react/use-streaming.d.ts.map +1 -0
- package/esm/src/agent/react/use-streaming.js +70 -0
- package/esm/src/agent/react/use-voice-input.d.ts +36 -0
- package/esm/src/agent/react/use-voice-input.d.ts.map +1 -0
- package/esm/src/agent/react/use-voice-input.js +132 -0
- package/esm/src/agent/runtime/constants.d.ts +5 -0
- package/esm/src/agent/runtime/constants.d.ts.map +1 -0
- package/esm/src/agent/runtime/constants.js +5 -0
- package/esm/src/agent/runtime/index.d.ts +78 -0
- package/esm/src/agent/runtime/index.d.ts.map +1 -0
- package/esm/src/agent/runtime/index.js +431 -0
- package/esm/src/agent/runtime/input-utils.d.ts +13 -0
- package/esm/src/agent/runtime/input-utils.d.ts.map +1 -0
- package/esm/src/agent/runtime/input-utils.js +33 -0
- package/esm/src/agent/runtime/message-converter.d.ts +39 -0
- package/esm/src/agent/runtime/message-converter.d.ts.map +1 -0
- package/esm/src/agent/runtime/message-converter.js +75 -0
- package/esm/src/agent/runtime/sse-utils.d.ts +17 -0
- package/esm/src/agent/runtime/sse-utils.d.ts.map +1 -0
- package/esm/src/agent/runtime/sse-utils.js +20 -0
- package/esm/src/agent/runtime/stream-handler.d.ts +23 -0
- package/esm/src/agent/runtime/stream-handler.d.ts.map +1 -0
- package/esm/src/agent/runtime/stream-handler.js +145 -0
- package/esm/src/agent/runtime/tool-helpers.d.ts +36 -0
- package/esm/src/agent/runtime/tool-helpers.d.ts.map +1 -0
- package/esm/src/agent/runtime/tool-helpers.js +73 -0
- package/esm/src/agent/streaming/index.d.ts +2 -0
- package/esm/src/agent/streaming/index.d.ts.map +1 -0
- package/esm/src/agent/streaming/index.js +1 -0
- package/esm/src/agent/streaming/stream-events.d.ts +137 -0
- package/esm/src/agent/streaming/stream-events.d.ts.map +1 -0
- package/esm/src/agent/streaming/stream-events.js +97 -0
- package/esm/src/agent/testing/agent-tester.d.ts +80 -0
- package/esm/src/agent/testing/agent-tester.d.ts.map +1 -0
- package/esm/src/agent/testing/agent-tester.js +169 -0
- package/esm/src/agent/testing/index.d.ts +8 -0
- package/esm/src/agent/testing/index.d.ts.map +1 -0
- package/esm/src/agent/testing/index.js +7 -0
- package/esm/src/agent/types.d.ts +146 -0
- package/esm/src/agent/types.d.ts.map +1 -0
- package/esm/src/agent/types.js +20 -0
- package/esm/src/build/asset-pipeline/css-optimizer/critical-css.d.ts +3 -0
- package/esm/src/build/asset-pipeline/css-optimizer/critical-css.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/css-optimizer/critical-css.js +37 -0
- package/esm/src/build/asset-pipeline/css-optimizer/css-bundle-cache.d.ts +23 -0
- package/esm/src/build/asset-pipeline/css-optimizer/css-bundle-cache.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/css-optimizer/css-bundle-cache.js +76 -0
- package/esm/src/build/asset-pipeline/css-optimizer/index.d.ts +36 -0
- package/esm/src/build/asset-pipeline/css-optimizer/index.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/css-optimizer/index.js +55 -0
- package/esm/src/build/asset-pipeline/css-optimizer/optimizer-service.d.ts +25 -0
- package/esm/src/build/asset-pipeline/css-optimizer/optimizer-service.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/css-optimizer/optimizer-service.js +163 -0
- package/esm/src/build/asset-pipeline/css-optimizer/strategies/index.d.ts +4 -0
- package/esm/src/build/asset-pipeline/css-optimizer/strategies/index.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/css-optimizer/strategies/index.js +3 -0
- package/esm/src/build/asset-pipeline/css-optimizer/strategies/lightning-strategy.d.ts +12 -0
- package/esm/src/build/asset-pipeline/css-optimizer/strategies/lightning-strategy.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/css-optimizer/strategies/lightning-strategy.js +55 -0
- package/esm/src/build/asset-pipeline/css-optimizer/strategies/minification-strategy.d.ts +8 -0
- package/esm/src/build/asset-pipeline/css-optimizer/strategies/minification-strategy.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/css-optimizer/strategies/minification-strategy.js +16 -0
- package/esm/src/build/asset-pipeline/css-optimizer/strategies/purge-strategy.d.ts +13 -0
- package/esm/src/build/asset-pipeline/css-optimizer/strategies/purge-strategy.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/css-optimizer/strategies/purge-strategy.js +69 -0
- package/esm/src/build/asset-pipeline/css-optimizer/types/index.d.ts +73 -0
- package/esm/src/build/asset-pipeline/css-optimizer/types/index.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/css-optimizer/types/index.js +1 -0
- package/esm/src/build/asset-pipeline/css-optimizer/utils.d.ts +17 -0
- package/esm/src/build/asset-pipeline/css-optimizer/utils.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/css-optimizer/utils.js +129 -0
- package/esm/src/build/asset-pipeline/image-optimizer/constants.d.ts +6 -0
- package/esm/src/build/asset-pipeline/image-optimizer/constants.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/image-optimizer/constants.js +13 -0
- package/esm/src/build/asset-pipeline/image-optimizer/format-processor.d.ts +3 -0
- package/esm/src/build/asset-pipeline/image-optimizer/format-processor.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/image-optimizer/format-processor.js +14 -0
- package/esm/src/build/asset-pipeline/image-optimizer/image-finder.d.ts +2 -0
- package/esm/src/build/asset-pipeline/image-optimizer/image-finder.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/image-optimizer/image-finder.js +28 -0
- package/esm/src/build/asset-pipeline/image-optimizer/index.d.ts +6 -0
- package/esm/src/build/asset-pipeline/image-optimizer/index.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/image-optimizer/index.js +10 -0
- package/esm/src/build/asset-pipeline/image-optimizer/manifest-manager.d.ts +4 -0
- package/esm/src/build/asset-pipeline/image-optimizer/manifest-manager.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/image-optimizer/manifest-manager.js +32 -0
- package/esm/src/build/asset-pipeline/image-optimizer/optimizer-core.d.ts +16 -0
- package/esm/src/build/asset-pipeline/image-optimizer/optimizer-core.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/image-optimizer/optimizer-core.js +137 -0
- package/esm/src/build/asset-pipeline/image-optimizer/sharp-loader.d.ts +3 -0
- package/esm/src/build/asset-pipeline/image-optimizer/sharp-loader.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/image-optimizer/sharp-loader.js +16 -0
- package/esm/src/build/asset-pipeline/image-optimizer/types.d.ts +75 -0
- package/esm/src/build/asset-pipeline/image-optimizer/types.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/image-optimizer/types.js +1 -0
- package/esm/src/build/asset-pipeline/image-optimizer/variant-generator.d.ts +4 -0
- package/esm/src/build/asset-pipeline/image-optimizer/variant-generator.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/image-optimizer/variant-generator.js +62 -0
- package/esm/src/build/asset-pipeline/index.d.ts +48 -0
- package/esm/src/build/asset-pipeline/index.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/index.js +141 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/batch-processor.d.ts +4 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/batch-processor.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/batch-processor.js +46 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/css-utils.d.ts +3 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/css-utils.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/css-utils.js +11 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/detector.d.ts +10 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/detector.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/detector.js +37 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/index.d.ts +7 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/index.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/index.js +5 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/lightning-processor.d.ts +3 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/lightning-processor.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/lightning-processor.js +40 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/processor.d.ts +7 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/processor.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/processor.js +65 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/types.d.ts +24 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/types.d.ts.map +1 -0
- package/esm/src/build/asset-pipeline/tailwind-processor/types.js +1 -0
- package/esm/src/build/bundler/code-splitter/build-context.d.ts +13 -0
- package/esm/src/build/bundler/code-splitter/build-context.d.ts.map +1 -0
- package/esm/src/build/bundler/code-splitter/build-context.js +77 -0
- package/esm/src/build/bundler/code-splitter/entry-points.d.ts +12 -0
- package/esm/src/build/bundler/code-splitter/entry-points.d.ts.map +1 -0
- package/esm/src/build/bundler/code-splitter/entry-points.js +19 -0
- package/esm/src/build/bundler/code-splitter/esbuild-plugin.d.ts +3 -0
- package/esm/src/build/bundler/code-splitter/esbuild-plugin.d.ts.map +1 -0
- package/esm/src/build/bundler/code-splitter/esbuild-plugin.js +26 -0
- package/esm/src/build/bundler/code-splitter/index.d.ts +17 -0
- package/esm/src/build/bundler/code-splitter/index.d.ts.map +1 -0
- package/esm/src/build/bundler/code-splitter/index.js +35 -0
- package/esm/src/build/bundler/code-splitter/manifest-builder.d.ts +21 -0
- package/esm/src/build/bundler/code-splitter/manifest-builder.d.ts.map +1 -0
- package/esm/src/build/bundler/code-splitter/manifest-builder.js +100 -0
- package/esm/src/build/bundler/code-splitter/splitter.d.ts +10 -0
- package/esm/src/build/bundler/code-splitter/splitter.d.ts.map +1 -0
- package/esm/src/build/bundler/code-splitter/splitter.js +68 -0
- package/esm/src/build/bundler/code-splitter/types.d.ts +41 -0
- package/esm/src/build/bundler/code-splitter/types.d.ts.map +1 -0
- package/esm/src/build/bundler/code-splitter/types.js +1 -0
- package/esm/src/build/bundler/index.d.ts +6 -0
- package/esm/src/build/bundler/index.d.ts.map +1 -0
- package/esm/src/build/bundler/index.js +5 -0
- package/esm/src/build/compiler/index.d.ts +15 -0
- package/esm/src/build/compiler/index.d.ts.map +1 -0
- package/esm/src/build/compiler/index.js +13 -0
- package/esm/src/build/compiler/mdx-compiler/code-generator.d.ts +3 -0
- package/esm/src/build/compiler/mdx-compiler/code-generator.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-compiler/code-generator.js +15 -0
- package/esm/src/build/compiler/mdx-compiler/compiler.d.ts +3 -0
- package/esm/src/build/compiler/mdx-compiler/compiler.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-compiler/compiler.js +32 -0
- package/esm/src/build/compiler/mdx-compiler/directory-compiler.d.ts +4 -0
- package/esm/src/build/compiler/mdx-compiler/directory-compiler.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-compiler/directory-compiler.js +30 -0
- package/esm/src/build/compiler/mdx-compiler/file-writer.d.ts +3 -0
- package/esm/src/build/compiler/mdx-compiler/file-writer.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-compiler/file-writer.js +10 -0
- package/esm/src/build/compiler/mdx-compiler/frontmatter-parser.d.ts +11 -0
- package/esm/src/build/compiler/mdx-compiler/frontmatter-parser.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-compiler/frontmatter-parser.js +75 -0
- package/esm/src/build/compiler/mdx-compiler/import-transformer.d.ts +3 -0
- package/esm/src/build/compiler/mdx-compiler/import-transformer.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-compiler/import-transformer.js +14 -0
- package/esm/src/build/compiler/mdx-compiler/index.d.ts +5 -0
- package/esm/src/build/compiler/mdx-compiler/index.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-compiler/index.js +3 -0
- package/esm/src/build/compiler/mdx-compiler/mdx-processor.d.ts +7 -0
- package/esm/src/build/compiler/mdx-compiler/mdx-processor.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-compiler/mdx-processor.js +28 -0
- package/esm/src/build/compiler/mdx-compiler/transpiler.d.ts +3 -0
- package/esm/src/build/compiler/mdx-compiler/transpiler.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-compiler/transpiler.js +12 -0
- package/esm/src/build/compiler/mdx-compiler/types.d.ts +19 -0
- package/esm/src/build/compiler/mdx-compiler/types.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-compiler/types.js +1 -0
- package/esm/src/build/compiler/mdx-compiler/validator.d.ts +5 -0
- package/esm/src/build/compiler/mdx-compiler/validator.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-compiler/validator.js +46 -0
- package/esm/src/build/compiler/mdx-compiler/watcher.d.ts +3 -0
- package/esm/src/build/compiler/mdx-compiler/watcher.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-compiler/watcher.js +50 -0
- package/esm/src/build/compiler/mdx-to-js.d.ts +23 -0
- package/esm/src/build/compiler/mdx-to-js.d.ts.map +1 -0
- package/esm/src/build/compiler/mdx-to-js.js +186 -0
- package/esm/src/build/config/environment.d.ts +20 -0
- package/esm/src/build/config/environment.d.ts.map +1 -0
- package/esm/src/build/config/environment.js +40 -0
- package/esm/src/build/embedded/preset.d.ts +17 -0
- package/esm/src/build/embedded/preset.d.ts.map +1 -0
- package/esm/src/build/embedded/preset.js +250 -0
- package/esm/src/build/index.d.ts +11 -0
- package/esm/src/build/index.d.ts.map +1 -0
- package/esm/src/build/index.js +5 -0
- package/esm/src/build/production-build/asset-generation.d.ts +18 -0
- package/esm/src/build/production-build/asset-generation.d.ts.map +1 -0
- package/esm/src/build/production-build/asset-generation.js +147 -0
- package/esm/src/build/production-build/build/build-cleanup.d.ts +7 -0
- package/esm/src/build/production-build/build/build-cleanup.d.ts.map +1 -0
- package/esm/src/build/production-build/build/build-cleanup.js +33 -0
- package/esm/src/build/production-build/build/build-executor.d.ts +35 -0
- package/esm/src/build/production-build/build/build-executor.d.ts.map +1 -0
- package/esm/src/build/production-build/build/build-executor.js +27 -0
- package/esm/src/build/production-build/build/build-initializer.d.ts +14 -0
- package/esm/src/build/production-build/build/build-initializer.d.ts.map +1 -0
- package/esm/src/build/production-build/build/build-initializer.js +42 -0
- package/esm/src/build/production-build/build/build-orchestrator.d.ts +20 -0
- package/esm/src/build/production-build/build/build-orchestrator.d.ts.map +1 -0
- package/esm/src/build/production-build/build/build-orchestrator.js +84 -0
- package/esm/src/build/production-build/build/build-setup.d.ts +3 -0
- package/esm/src/build/production-build/build/build-setup.d.ts.map +1 -0
- package/esm/src/build/production-build/build/build-setup.js +34 -0
- package/esm/src/build/production-build/build/code-splitter-orchestrator.d.ts +19 -0
- package/esm/src/build/production-build/build/code-splitter-orchestrator.d.ts.map +1 -0
- package/esm/src/build/production-build/build/code-splitter-orchestrator.js +36 -0
- package/esm/src/build/production-build/build/index.d.ts +31 -0
- package/esm/src/build/production-build/build/index.d.ts.map +1 -0
- package/esm/src/build/production-build/build/index.js +30 -0
- package/esm/src/build/production-build/build/output-generator.d.ts +50 -0
- package/esm/src/build/production-build/build/output-generator.d.ts.map +1 -0
- package/esm/src/build/production-build/build/output-generator.js +71 -0
- package/esm/src/build/production-build/build/route-collector.d.ts +16 -0
- package/esm/src/build/production-build/build/route-collector.d.ts.map +1 -0
- package/esm/src/build/production-build/build/route-collector.js +25 -0
- package/esm/src/build/production-build/client-runtime.d.ts +27 -0
- package/esm/src/build/production-build/client-runtime.d.ts.map +1 -0
- package/esm/src/build/production-build/client-runtime.js +302 -0
- package/esm/src/build/production-build/index.d.ts +6 -0
- package/esm/src/build/production-build/index.d.ts.map +1 -0
- package/esm/src/build/production-build/index.js +5 -0
- package/esm/src/build/production-build/manifest.d.ts +50 -0
- package/esm/src/build/production-build/manifest.d.ts.map +1 -0
- package/esm/src/build/production-build/manifest.js +62 -0
- package/esm/src/build/production-build/static-generation.d.ts +46 -0
- package/esm/src/build/production-build/static-generation.d.ts.map +1 -0
- package/esm/src/build/production-build/static-generation.js +140 -0
- package/esm/src/build/production-build/templates.d.ts +16 -0
- package/esm/src/build/production-build/templates.d.ts.map +1 -0
- package/esm/src/build/production-build/templates.js +89 -0
- package/esm/src/build/renderer/index.d.ts +8 -0
- package/esm/src/build/renderer/index.d.ts.map +1 -0
- package/esm/src/build/renderer/index.js +8 -0
- package/esm/src/build/renderer/services/css-bundler.d.ts +8 -0
- package/esm/src/build/renderer/services/css-bundler.d.ts.map +1 -0
- package/esm/src/build/renderer/services/css-bundler.js +49 -0
- package/esm/src/build/renderer/services/mdx-bundler.d.ts +13 -0
- package/esm/src/build/renderer/services/mdx-bundler.d.ts.map +1 -0
- package/esm/src/build/renderer/services/mdx-bundler.js +183 -0
- package/esm/src/build/renderer/services/optimizer.d.ts +6 -0
- package/esm/src/build/renderer/services/optimizer.d.ts.map +1 -0
- package/esm/src/build/renderer/services/optimizer.js +34 -0
- package/esm/src/build/renderer/services/script-bundler.d.ts +14 -0
- package/esm/src/build/renderer/services/script-bundler.d.ts.map +1 -0
- package/esm/src/build/renderer/services/script-bundler.js +158 -0
- package/esm/src/build/renderer/types/bundler-types.d.ts +6 -0
- package/esm/src/build/renderer/types/bundler-types.d.ts.map +1 -0
- package/esm/src/build/renderer/types/bundler-types.js +5 -0
- package/esm/src/build/renderer/utils/import-utils.d.ts +20 -0
- package/esm/src/build/renderer/utils/import-utils.d.ts.map +1 -0
- package/esm/src/build/renderer/utils/import-utils.js +65 -0
- package/esm/src/build/renderer/utils/loader-utils.d.ts +7 -0
- package/esm/src/build/renderer/utils/loader-utils.d.ts.map +1 -0
- package/esm/src/build/renderer/utils/loader-utils.js +37 -0
- package/esm/src/build/renderer/utils/plugin-utils.d.ts +3 -0
- package/esm/src/build/renderer/utils/plugin-utils.d.ts.map +1 -0
- package/esm/src/build/renderer/utils/plugin-utils.js +5 -0
- package/esm/src/build/utils/asset-utils.d.ts +21 -0
- package/esm/src/build/utils/asset-utils.d.ts.map +1 -0
- package/esm/src/build/utils/asset-utils.js +100 -0
- package/esm/src/build/utils/file-types.d.ts +99 -0
- package/esm/src/build/utils/file-types.d.ts.map +1 -0
- package/esm/src/build/utils/file-types.js +159 -0
- package/esm/src/build/vendor-bundle.d.ts +32 -0
- package/esm/src/build/vendor-bundle.d.ts.map +1 -0
- package/esm/src/build/vendor-bundle.js +108 -0
- package/esm/src/build/vendor-cache.d.ts +67 -0
- package/esm/src/build/vendor-cache.d.ts.map +1 -0
- package/esm/src/build/vendor-cache.js +148 -0
- package/esm/src/cache/backend.d.ts +142 -0
- package/esm/src/cache/backend.d.ts.map +1 -0
- package/esm/src/cache/backend.js +469 -0
- package/esm/src/cache/cache-key-builder.d.ts +28 -0
- package/esm/src/cache/cache-key-builder.d.ts.map +1 -0
- package/esm/src/cache/cache-key-builder.js +84 -0
- package/esm/src/cache/distributed-cache-init.d.ts +31 -0
- package/esm/src/cache/distributed-cache-init.d.ts.map +1 -0
- package/esm/src/cache/distributed-cache-init.js +91 -0
- package/esm/src/cache/index.d.ts +5 -0
- package/esm/src/cache/index.d.ts.map +1 -0
- package/esm/src/cache/index.js +4 -0
- package/esm/src/cache/keys.d.ts +115 -0
- package/esm/src/cache/keys.d.ts.map +1 -0
- package/esm/src/cache/keys.js +255 -0
- package/esm/src/cache/registry.d.ts +74 -0
- package/esm/src/cache/registry.d.ts.map +1 -0
- package/esm/src/cache/registry.js +401 -0
- package/esm/src/cache/request-cache-batcher.d.ts +23 -0
- package/esm/src/cache/request-cache-batcher.d.ts.map +1 -0
- package/esm/src/cache/request-cache-batcher.js +106 -0
- package/esm/src/cli/auth/browser.d.ts +4 -0
- package/esm/src/cli/auth/browser.d.ts.map +1 -0
- package/esm/src/cli/auth/browser.js +25 -0
- package/esm/src/cli/auth/callback-server.d.ts +12 -0
- package/esm/src/cli/auth/callback-server.d.ts.map +1 -0
- package/esm/src/cli/auth/callback-server.js +240 -0
- package/esm/src/cli/auth/constants.d.ts +10 -0
- package/esm/src/cli/auth/constants.d.ts.map +1 -0
- package/esm/src/cli/auth/constants.js +11 -0
- package/esm/src/cli/auth/index.d.ts +6 -0
- package/esm/src/cli/auth/index.d.ts.map +1 -0
- package/esm/src/cli/auth/index.js +5 -0
- package/esm/src/cli/auth/login.d.ts +15 -0
- package/esm/src/cli/auth/login.d.ts.map +1 -0
- package/esm/src/cli/auth/login.js +233 -0
- package/esm/src/cli/auth/token-store.d.ts +6 -0
- package/esm/src/cli/auth/token-store.d.ts.map +1 -0
- package/esm/src/cli/auth/token-store.js +54 -0
- package/esm/src/cli/commands/analyze-chunks.d.ts +6 -0
- package/esm/src/cli/commands/analyze-chunks.d.ts.map +1 -0
- package/esm/src/cli/commands/analyze-chunks.js +50 -0
- package/esm/src/cli/commands/build/config-display.d.ts +4 -0
- package/esm/src/cli/commands/build/config-display.d.ts.map +1 -0
- package/esm/src/cli/commands/build/config-display.js +29 -0
- package/esm/src/cli/commands/build/error-handler.d.ts +2 -0
- package/esm/src/cli/commands/build/error-handler.d.ts.map +1 -0
- package/esm/src/cli/commands/build/error-handler.js +22 -0
- package/esm/src/cli/commands/build/index.d.ts +3 -0
- package/esm/src/cli/commands/build/index.d.ts.map +1 -0
- package/esm/src/cli/commands/build/index.js +36 -0
- package/esm/src/cli/commands/build/stats-display.d.ts +3 -0
- package/esm/src/cli/commands/build/stats-display.d.ts.map +1 -0
- package/esm/src/cli/commands/build/stats-display.js +27 -0
- package/esm/src/cli/commands/build/types.d.ts +2 -0
- package/esm/src/cli/commands/build/types.d.ts.map +1 -0
- package/esm/src/cli/commands/build/types.js +1 -0
- package/esm/src/cli/commands/build.d.ts +3 -0
- package/esm/src/cli/commands/build.d.ts.map +1 -0
- package/esm/src/cli/commands/build.js +1 -0
- package/esm/src/cli/commands/clean.d.ts +10 -0
- package/esm/src/cli/commands/clean.d.ts.map +1 -0
- package/esm/src/cli/commands/clean.js +105 -0
- package/esm/src/cli/commands/demo/demo.d.ts +12 -0
- package/esm/src/cli/commands/demo/demo.d.ts.map +1 -0
- package/esm/src/cli/commands/demo/demo.js +497 -0
- package/esm/src/cli/commands/demo/index.d.ts +8 -0
- package/esm/src/cli/commands/demo/index.d.ts.map +1 -0
- package/esm/src/cli/commands/demo/index.js +7 -0
- package/esm/src/cli/commands/demo/steps.d.ts +10 -0
- package/esm/src/cli/commands/demo/steps.d.ts.map +1 -0
- package/esm/src/cli/commands/demo/steps.js +59 -0
- package/esm/src/cli/commands/deploy.d.ts +93 -0
- package/esm/src/cli/commands/deploy.d.ts.map +1 -0
- package/esm/src/cli/commands/deploy.js +115 -0
- package/esm/src/cli/commands/dev.d.ts +16 -0
- package/esm/src/cli/commands/dev.d.ts.map +1 -0
- package/esm/src/cli/commands/dev.js +259 -0
- package/esm/src/cli/commands/doctor/ai-checks.d.ts +6 -0
- package/esm/src/cli/commands/doctor/ai-checks.d.ts.map +1 -0
- package/esm/src/cli/commands/doctor/ai-checks.js +56 -0
- package/esm/src/cli/commands/doctor/index.d.ts +4 -0
- package/esm/src/cli/commands/doctor/index.d.ts.map +1 -0
- package/esm/src/cli/commands/doctor/index.js +62 -0
- package/esm/src/cli/commands/doctor/project-structure.d.ts +5 -0
- package/esm/src/cli/commands/doctor/project-structure.d.ts.map +1 -0
- package/esm/src/cli/commands/doctor/project-structure.js +49 -0
- package/esm/src/cli/commands/doctor/server-checks.d.ts +5 -0
- package/esm/src/cli/commands/doctor/server-checks.d.ts.map +1 -0
- package/esm/src/cli/commands/doctor/server-checks.js +125 -0
- package/esm/src/cli/commands/doctor/types.d.ts +7 -0
- package/esm/src/cli/commands/doctor/types.d.ts.map +1 -0
- package/esm/src/cli/commands/doctor/types.js +1 -0
- package/esm/src/cli/commands/doctor/version-checks.d.ts +4 -0
- package/esm/src/cli/commands/doctor/version-checks.d.ts.map +1 -0
- package/esm/src/cli/commands/doctor/version-checks.js +48 -0
- package/esm/src/cli/commands/generate/integration-generator.d.ts +29 -0
- package/esm/src/cli/commands/generate/integration-generator.d.ts.map +1 -0
- package/esm/src/cli/commands/generate/integration-generator.js +524 -0
- package/esm/src/cli/commands/generate.d.ts +2 -0
- package/esm/src/cli/commands/generate.d.ts.map +1 -0
- package/esm/src/cli/commands/generate.js +147 -0
- package/esm/src/cli/commands/init/config-generator.d.ts +2 -0
- package/esm/src/cli/commands/init/config-generator.d.ts.map +1 -0
- package/esm/src/cli/commands/init/config-generator.js +24 -0
- package/esm/src/cli/commands/init/index.d.ts +8 -0
- package/esm/src/cli/commands/init/index.d.ts.map +1 -0
- package/esm/src/cli/commands/init/index.js +6 -0
- package/esm/src/cli/commands/init/init-command.d.ts +10 -0
- package/esm/src/cli/commands/init/init-command.d.ts.map +1 -0
- package/esm/src/cli/commands/init/init-command.js +335 -0
- package/esm/src/cli/commands/init/interactive-wizard.d.ts +13 -0
- package/esm/src/cli/commands/init/interactive-wizard.d.ts.map +1 -0
- package/esm/src/cli/commands/init/interactive-wizard.js +172 -0
- package/esm/src/cli/commands/init/types.d.ts +15 -0
- package/esm/src/cli/commands/init/types.d.ts.map +1 -0
- package/esm/src/cli/commands/init/types.js +1 -0
- package/esm/src/cli/commands/install/detect.d.ts +8 -0
- package/esm/src/cli/commands/install/detect.d.ts.map +1 -0
- package/esm/src/cli/commands/install/detect.js +40 -0
- package/esm/src/cli/commands/install/index.d.ts +10 -0
- package/esm/src/cli/commands/install/index.d.ts.map +1 -0
- package/esm/src/cli/commands/install/index.js +8 -0
- package/esm/src/cli/commands/install/install.d.ts +9 -0
- package/esm/src/cli/commands/install/install.d.ts.map +1 -0
- package/esm/src/cli/commands/install/install.js +183 -0
- package/esm/src/cli/commands/install/registry.d.ts +7 -0
- package/esm/src/cli/commands/install/registry.d.ts.map +1 -0
- package/esm/src/cli/commands/install/registry.js +65 -0
- package/esm/src/cli/commands/install/types.d.ts +72 -0
- package/esm/src/cli/commands/install/types.d.ts.map +1 -0
- package/esm/src/cli/commands/install/types.js +27 -0
- package/esm/src/cli/commands/install/uninstall.d.ts +10 -0
- package/esm/src/cli/commands/install/uninstall.d.ts.map +1 -0
- package/esm/src/cli/commands/install/uninstall.js +212 -0
- package/esm/src/cli/commands/issues.d.ts +32 -0
- package/esm/src/cli/commands/issues.d.ts.map +1 -0
- package/esm/src/cli/commands/issues.js +313 -0
- package/esm/src/cli/commands/lock.d.ts +11 -0
- package/esm/src/cli/commands/lock.d.ts.map +1 -0
- package/esm/src/cli/commands/lock.js +164 -0
- package/esm/src/cli/commands/main.d.ts +11 -0
- package/esm/src/cli/commands/main.d.ts.map +1 -0
- package/esm/src/cli/commands/main.js +225 -0
- package/esm/src/cli/commands/merge.d.ts +71 -0
- package/esm/src/cli/commands/merge.d.ts.map +1 -0
- package/esm/src/cli/commands/merge.js +124 -0
- package/esm/src/cli/commands/new/fast-scaffold.d.ts +24 -0
- package/esm/src/cli/commands/new/fast-scaffold.d.ts.map +1 -0
- package/esm/src/cli/commands/new/fast-scaffold.js +140 -0
- package/esm/src/cli/commands/new/reserve-slug.d.ts +9 -0
- package/esm/src/cli/commands/new/reserve-slug.d.ts.map +1 -0
- package/esm/src/cli/commands/new/reserve-slug.js +74 -0
- package/esm/src/cli/commands/new-tui.d.ts +9 -0
- package/esm/src/cli/commands/new-tui.d.ts.map +1 -0
- package/esm/src/cli/commands/new-tui.js +200 -0
- package/esm/src/cli/commands/new.d.ts +37 -0
- package/esm/src/cli/commands/new.d.ts.map +1 -0
- package/esm/src/cli/commands/new.js +176 -0
- package/esm/src/cli/commands/pull.d.ts +83 -0
- package/esm/src/cli/commands/pull.d.ts.map +1 -0
- package/esm/src/cli/commands/pull.js +289 -0
- package/esm/src/cli/commands/push.d.ts +73 -0
- package/esm/src/cli/commands/push.d.ts.map +1 -0
- package/esm/src/cli/commands/push.js +256 -0
- package/esm/src/cli/commands/routes.d.ts +4 -0
- package/esm/src/cli/commands/routes.d.ts.map +1 -0
- package/esm/src/cli/commands/routes.js +73 -0
- package/esm/src/cli/commands/studio.d.ts +24 -0
- package/esm/src/cli/commands/studio.d.ts.map +1 -0
- package/esm/src/cli/commands/studio.js +74 -0
- package/esm/src/cli/commands/up.d.ts +19 -0
- package/esm/src/cli/commands/up.d.ts.map +1 -0
- package/esm/src/cli/commands/up.js +176 -0
- package/esm/src/cli/discovery/config-validator.d.ts +9 -0
- package/esm/src/cli/discovery/config-validator.d.ts.map +1 -0
- package/esm/src/cli/discovery/config-validator.js +35 -0
- package/esm/src/cli/discovery/index.d.ts +32 -0
- package/esm/src/cli/discovery/index.d.ts.map +1 -0
- package/esm/src/cli/discovery/index.js +519 -0
- package/esm/src/cli/help/command-definitions.d.ts +3 -0
- package/esm/src/cli/help/command-definitions.d.ts.map +1 -0
- package/esm/src/cli/help/command-definitions.js +724 -0
- package/esm/src/cli/help/command-help.d.ts +2 -0
- package/esm/src/cli/help/command-help.d.ts.map +1 -0
- package/esm/src/cli/help/command-help.js +39 -0
- package/esm/src/cli/help/formatters.d.ts +16 -0
- package/esm/src/cli/help/formatters.d.ts.map +1 -0
- package/esm/src/cli/help/formatters.js +72 -0
- package/esm/src/cli/help/index.d.ts +6 -0
- package/esm/src/cli/help/index.d.ts.map +1 -0
- package/esm/src/cli/help/index.js +4 -0
- package/esm/src/cli/help/logo.d.ts +2 -0
- package/esm/src/cli/help/logo.d.ts.map +1 -0
- package/esm/src/cli/help/logo.js +4 -0
- package/esm/src/cli/help/main-help.d.ts +2 -0
- package/esm/src/cli/help/main-help.d.ts.map +1 -0
- package/esm/src/cli/help/main-help.js +34 -0
- package/esm/src/cli/help/tips.d.ts +5 -0
- package/esm/src/cli/help/tips.d.ts.map +1 -0
- package/esm/src/cli/help/tips.js +29 -0
- package/esm/src/cli/help/types.d.ts +15 -0
- package/esm/src/cli/help/types.d.ts.map +1 -0
- package/esm/src/cli/help/types.js +1 -0
- package/esm/src/cli/index/arg-parser.d.ts +4 -0
- package/esm/src/cli/index/arg-parser.d.ts.map +1 -0
- package/esm/src/cli/index/arg-parser.js +81 -0
- package/esm/src/cli/index/build-handler.d.ts +8 -0
- package/esm/src/cli/index/build-handler.d.ts.map +1 -0
- package/esm/src/cli/index/build-handler.js +48 -0
- package/esm/src/cli/index/cli-main.d.ts +3 -0
- package/esm/src/cli/index/cli-main.d.ts.map +1 -0
- package/esm/src/cli/index/cli-main.js +10 -0
- package/esm/src/cli/index/command-router.d.ts +13 -0
- package/esm/src/cli/index/command-router.d.ts.map +1 -0
- package/esm/src/cli/index/command-router.js +439 -0
- package/esm/src/cli/index/dev-handler.d.ts +3 -0
- package/esm/src/cli/index/dev-handler.d.ts.map +1 -0
- package/esm/src/cli/index/dev-handler.js +34 -0
- package/esm/src/cli/index/generate-handler.d.ts +8 -0
- package/esm/src/cli/index/generate-handler.d.ts.map +1 -0
- package/esm/src/cli/index/generate-handler.js +25 -0
- package/esm/src/cli/index/index.d.ts +17 -0
- package/esm/src/cli/index/index.d.ts.map +1 -0
- package/esm/src/cli/index/index.js +15 -0
- package/esm/src/cli/index/studio-handler.d.ts +3 -0
- package/esm/src/cli/index/studio-handler.d.ts.map +1 -0
- package/esm/src/cli/index/studio-handler.js +21 -0
- package/esm/src/cli/index/types.d.ts +44 -0
- package/esm/src/cli/index/types.d.ts.map +1 -0
- package/esm/src/cli/index/types.js +1 -0
- package/esm/src/cli/index.d.ts +2 -0
- package/esm/src/cli/index.d.ts.map +1 -0
- package/esm/src/cli/index.js +1 -0
- package/esm/src/cli/main.d.ts +7 -0
- package/esm/src/cli/main.d.ts.map +1 -0
- package/esm/src/cli/main.js +10 -0
- package/esm/src/cli/mcp/advanced-tools.d.ts +387 -0
- package/esm/src/cli/mcp/advanced-tools.d.ts.map +1 -0
- package/esm/src/cli/mcp/advanced-tools.js +1486 -0
- package/esm/src/cli/mcp/error-collector.d.ts +63 -0
- package/esm/src/cli/mcp/error-collector.d.ts.map +1 -0
- package/esm/src/cli/mcp/error-collector.js +159 -0
- package/esm/src/cli/mcp/log-buffer.d.ts +45 -0
- package/esm/src/cli/mcp/log-buffer.d.ts.map +1 -0
- package/esm/src/cli/mcp/log-buffer.js +158 -0
- package/esm/src/cli/mcp/remote-file-tools.d.ts +310 -0
- package/esm/src/cli/mcp/remote-file-tools.d.ts.map +1 -0
- package/esm/src/cli/mcp/remote-file-tools.js +452 -0
- package/esm/src/cli/mcp/server.d.ts +36 -0
- package/esm/src/cli/mcp/server.d.ts.map +1 -0
- package/esm/src/cli/mcp/server.js +478 -0
- package/esm/src/cli/mcp/tools.d.ts +99 -0
- package/esm/src/cli/mcp/tools.d.ts.map +1 -0
- package/esm/src/cli/mcp/tools.js +146 -0
- package/esm/src/cli/shared/args.d.ts +88 -0
- package/esm/src/cli/shared/args.d.ts.map +1 -0
- package/esm/src/cli/shared/args.js +87 -0
- package/esm/src/cli/shared/config.d.ts +29 -0
- package/esm/src/cli/shared/config.d.ts.map +1 -0
- package/esm/src/cli/shared/config.js +124 -0
- package/esm/src/cli/sync/ignore.d.ts +22 -0
- package/esm/src/cli/sync/ignore.d.ts.map +1 -0
- package/esm/src/cli/sync/ignore.js +122 -0
- package/esm/src/cli/sync/index.d.ts +9 -0
- package/esm/src/cli/sync/index.d.ts.map +1 -0
- package/esm/src/cli/sync/index.js +8 -0
- package/esm/src/cli/sync/project-discovery.d.ts +28 -0
- package/esm/src/cli/sync/project-discovery.d.ts.map +1 -0
- package/esm/src/cli/sync/project-discovery.js +87 -0
- package/esm/src/cli/templates/feature-loader.d.ts +58 -0
- package/esm/src/cli/templates/feature-loader.d.ts.map +1 -0
- package/esm/src/cli/templates/feature-loader.js +169 -0
- package/esm/src/cli/templates/index.d.ts +31 -0
- package/esm/src/cli/templates/index.d.ts.map +1 -0
- package/esm/src/cli/templates/index.js +65 -0
- package/esm/src/cli/templates/integration-loader.d.ts +79 -0
- package/esm/src/cli/templates/integration-loader.d.ts.map +1 -0
- package/esm/src/cli/templates/integration-loader.js +665 -0
- package/esm/src/cli/templates/loader.d.ts +15 -0
- package/esm/src/cli/templates/loader.d.ts.map +1 -0
- package/esm/src/cli/templates/loader.js +41 -0
- package/esm/src/cli/templates/manifest.d.ts +714 -0
- package/esm/src/cli/templates/manifest.d.ts.map +1 -0
- package/esm/src/cli/templates/manifest.js +712 -0
- package/esm/src/cli/templates/types.d.ts +85 -0
- package/esm/src/cli/templates/types.d.ts.map +1 -0
- package/esm/src/cli/templates/types.js +4 -0
- package/esm/src/cli/ui/animated-text.d.ts +25 -0
- package/esm/src/cli/ui/animated-text.d.ts.map +1 -0
- package/esm/src/cli/ui/animated-text.js +57 -0
- package/esm/src/cli/ui/ansi.d.ts +51 -0
- package/esm/src/cli/ui/ansi.d.ts.map +1 -0
- package/esm/src/cli/ui/ansi.js +55 -0
- package/esm/src/cli/ui/box.d.ts +93 -0
- package/esm/src/cli/ui/box.d.ts.map +1 -0
- package/esm/src/cli/ui/box.js +189 -0
- package/esm/src/cli/ui/colors.d.ts +23 -0
- package/esm/src/cli/ui/colors.d.ts.map +1 -0
- package/esm/src/cli/ui/colors.js +134 -0
- package/esm/src/cli/ui/components/banner.d.ts +26 -0
- package/esm/src/cli/ui/components/banner.d.ts.map +1 -0
- package/esm/src/cli/ui/components/banner.js +111 -0
- package/esm/src/cli/ui/components/index.d.ts +4 -0
- package/esm/src/cli/ui/components/index.d.ts.map +1 -0
- package/esm/src/cli/ui/components/index.js +3 -0
- package/esm/src/cli/ui/components/shortcuts.d.ts +9 -0
- package/esm/src/cli/ui/components/shortcuts.d.ts.map +1 -0
- package/esm/src/cli/ui/components/shortcuts.js +19 -0
- package/esm/src/cli/ui/components/table.d.ts +57 -0
- package/esm/src/cli/ui/components/table.d.ts.map +1 -0
- package/esm/src/cli/ui/components/table.js +126 -0
- package/esm/src/cli/ui/constants.d.ts +15 -0
- package/esm/src/cli/ui/constants.d.ts.map +1 -0
- package/esm/src/cli/ui/constants.js +14 -0
- package/esm/src/cli/ui/dot-matrix.d.ts +124 -0
- package/esm/src/cli/ui/dot-matrix.d.ts.map +1 -0
- package/esm/src/cli/ui/dot-matrix.js +295 -0
- package/esm/src/cli/ui/index.d.ts +9 -0
- package/esm/src/cli/ui/index.d.ts.map +1 -0
- package/esm/src/cli/ui/index.js +8 -0
- package/esm/src/cli/ui/keyboard.d.ts +38 -0
- package/esm/src/cli/ui/keyboard.d.ts.map +1 -0
- package/esm/src/cli/ui/keyboard.js +114 -0
- package/esm/src/cli/ui/layout.d.ts +52 -0
- package/esm/src/cli/ui/layout.d.ts.map +1 -0
- package/esm/src/cli/ui/layout.js +131 -0
- package/esm/src/cli/ui/progress.d.ts +36 -0
- package/esm/src/cli/ui/progress.d.ts.map +1 -0
- package/esm/src/cli/ui/progress.js +167 -0
- package/esm/src/cli/ui/tui.d.ts +36 -0
- package/esm/src/cli/ui/tui.d.ts.map +1 -0
- package/esm/src/cli/ui/tui.js +231 -0
- package/esm/src/cli/utils/env-prompt.d.ts +34 -0
- package/esm/src/cli/utils/env-prompt.d.ts.map +1 -0
- package/esm/src/cli/utils/env-prompt.js +144 -0
- package/esm/src/cli/utils/index.d.ts +36 -0
- package/esm/src/cli/utils/index.d.ts.map +1 -0
- package/esm/src/cli/utils/index.js +240 -0
- package/esm/src/cli/utils/package-manager.d.ts +32 -0
- package/esm/src/cli/utils/package-manager.d.ts.map +1 -0
- package/esm/src/cli/utils/package-manager.js +109 -0
- package/esm/src/cli/utils/terminal-select.d.ts +18 -0
- package/esm/src/cli/utils/terminal-select.d.ts.map +1 -0
- package/esm/src/cli/utils/terminal-select.js +245 -0
- package/esm/src/config/defaults.d.ts +38 -0
- package/esm/src/config/defaults.d.ts.map +1 -0
- package/esm/src/config/defaults.js +64 -0
- package/esm/src/config/define-config.d.ts +7 -0
- package/esm/src/config/define-config.d.ts.map +1 -0
- package/esm/src/config/define-config.js +49 -0
- package/esm/src/config/env.d.ts +61 -0
- package/esm/src/config/env.d.ts.map +1 -0
- package/esm/src/config/env.js +104 -0
- package/esm/src/config/index.d.ts +12 -0
- package/esm/src/config/index.d.ts.map +1 -0
- package/esm/src/config/index.js +8 -0
- package/esm/src/config/loader.d.ts +26 -0
- package/esm/src/config/loader.d.ts.map +1 -0
- package/esm/src/config/loader.js +332 -0
- package/esm/src/config/network-defaults.d.ts +29 -0
- package/esm/src/config/network-defaults.d.ts.map +1 -0
- package/esm/src/config/network-defaults.js +40 -0
- package/esm/src/config/runtime-config.d.ts +56 -0
- package/esm/src/config/runtime-config.d.ts.map +1 -0
- package/esm/src/config/runtime-config.js +133 -0
- package/esm/src/config/runtime-env.d.ts +62 -0
- package/esm/src/config/runtime-env.d.ts.map +1 -0
- package/esm/src/config/runtime-env.js +111 -0
- package/esm/src/config/schema.d.ts +1196 -0
- package/esm/src/config/schema.d.ts.map +1 -0
- package/esm/src/config/schema.js +343 -0
- package/esm/src/config/types.d.ts +330 -0
- package/esm/src/config/types.d.ts.map +1 -0
- package/esm/src/config/types.js +1 -0
- package/esm/src/data/data-fetcher.d.ts +13 -0
- package/esm/src/data/data-fetcher.d.ts.map +1 -0
- package/esm/src/data/data-fetcher.js +51 -0
- package/esm/src/data/data-fetching-cache.d.ts +12 -0
- package/esm/src/data/data-fetching-cache.d.ts.map +1 -0
- package/esm/src/data/data-fetching-cache.js +47 -0
- package/esm/src/data/helpers.d.ts +4 -0
- package/esm/src/data/helpers.d.ts.map +1 -0
- package/esm/src/data/helpers.js +6 -0
- package/esm/src/data/index.d.ts +5 -0
- package/esm/src/data/index.d.ts.map +1 -0
- package/esm/src/data/index.js +3 -0
- package/esm/src/data/server-data-fetcher.d.ts +9 -0
- package/esm/src/data/server-data-fetcher.d.ts.map +1 -0
- package/esm/src/data/server-data-fetcher.js +53 -0
- package/esm/src/data/static-data-fetcher.d.ts +15 -0
- package/esm/src/data/static-data-fetcher.d.ts.map +1 -0
- package/esm/src/data/static-data-fetcher.js +155 -0
- package/esm/src/data/static-paths-fetcher.d.ts +5 -0
- package/esm/src/data/static-paths-fetcher.d.ts.map +1 -0
- package/esm/src/data/static-paths-fetcher.js +24 -0
- package/esm/src/data/types.d.ts +35 -0
- package/esm/src/data/types.d.ts.map +1 -0
- package/esm/src/data/types.js +1 -0
- package/esm/src/embeddings/base.d.ts +19 -0
- package/esm/src/embeddings/base.d.ts.map +1 -0
- package/esm/src/embeddings/base.js +75 -0
- package/esm/src/embeddings/index.d.ts +19 -0
- package/esm/src/embeddings/index.d.ts.map +1 -0
- package/esm/src/embeddings/index.js +38 -0
- package/esm/src/embeddings/providers/cohere.d.ts +12 -0
- package/esm/src/embeddings/providers/cohere.d.ts.map +1 -0
- package/esm/src/embeddings/providers/cohere.js +47 -0
- package/esm/src/embeddings/providers/openai.d.ts +12 -0
- package/esm/src/embeddings/providers/openai.d.ts.map +1 -0
- package/esm/src/embeddings/providers/openai.js +49 -0
- package/esm/src/embeddings/providers/voyageai.d.ts +12 -0
- package/esm/src/embeddings/providers/voyageai.d.ts.map +1 -0
- package/esm/src/embeddings/providers/voyageai.js +46 -0
- package/esm/src/embeddings/types.d.ts +33 -0
- package/esm/src/embeddings/types.d.ts.map +1 -0
- package/esm/src/embeddings/types.js +1 -0
- package/esm/src/errors/agent-errors.d.ts +17 -0
- package/esm/src/errors/agent-errors.d.ts.map +1 -0
- package/esm/src/errors/agent-errors.js +37 -0
- package/esm/src/errors/build-errors.d.ts +8 -0
- package/esm/src/errors/build-errors.d.ts.map +1 -0
- package/esm/src/errors/build-errors.js +13 -0
- package/esm/src/errors/catalog/build-errors.d.ts +3 -0
- package/esm/src/errors/catalog/build-errors.d.ts.map +1 -0
- package/esm/src/errors/catalog/build-errors.js +55 -0
- package/esm/src/errors/catalog/config-errors.d.ts +3 -0
- package/esm/src/errors/catalog/config-errors.d.ts.map +1 -0
- package/esm/src/errors/catalog/config-errors.js +83 -0
- package/esm/src/errors/catalog/deployment-errors.d.ts +3 -0
- package/esm/src/errors/catalog/deployment-errors.d.ts.map +1 -0
- package/esm/src/errors/catalog/deployment-errors.js +20 -0
- package/esm/src/errors/catalog/dev-errors.d.ts +3 -0
- package/esm/src/errors/catalog/dev-errors.d.ts.map +1 -0
- package/esm/src/errors/catalog/dev-errors.js +16 -0
- package/esm/src/errors/catalog/factory.d.ts +7 -0
- package/esm/src/errors/catalog/factory.d.ts.map +1 -0
- package/esm/src/errors/catalog/factory.js +11 -0
- package/esm/src/errors/catalog/general-errors.d.ts +3 -0
- package/esm/src/errors/catalog/general-errors.d.ts.map +1 -0
- package/esm/src/errors/catalog/general-errors.js +30 -0
- package/esm/src/errors/catalog/index.d.ts +19 -0
- package/esm/src/errors/catalog/index.d.ts.map +1 -0
- package/esm/src/errors/catalog/index.js +37 -0
- package/esm/src/errors/catalog/module-errors.d.ts +3 -0
- package/esm/src/errors/catalog/module-errors.d.ts.map +1 -0
- package/esm/src/errors/catalog/module-errors.js +60 -0
- package/esm/src/errors/catalog/route-errors.d.ts +3 -0
- package/esm/src/errors/catalog/route-errors.d.ts.map +1 -0
- package/esm/src/errors/catalog/route-errors.js +42 -0
- package/esm/src/errors/catalog/rsc-errors.d.ts +3 -0
- package/esm/src/errors/catalog/rsc-errors.d.ts.map +1 -0
- package/esm/src/errors/catalog/rsc-errors.js +56 -0
- package/esm/src/errors/catalog/runtime-errors.d.ts +3 -0
- package/esm/src/errors/catalog/runtime-errors.d.ts.map +1 -0
- package/esm/src/errors/catalog/runtime-errors.js +64 -0
- package/esm/src/errors/catalog/server-errors.d.ts +3 -0
- package/esm/src/errors/catalog/server-errors.d.ts.map +1 -0
- package/esm/src/errors/catalog/server-errors.js +42 -0
- package/esm/src/errors/catalog/types.d.ts +14 -0
- package/esm/src/errors/catalog/types.d.ts.map +1 -0
- package/esm/src/errors/catalog/types.js +1 -0
- package/esm/src/errors/error-codes.d.ts +65 -0
- package/esm/src/errors/error-codes.d.ts.map +1 -0
- package/esm/src/errors/error-codes.js +105 -0
- package/esm/src/errors/error-context.d.ts +50 -0
- package/esm/src/errors/error-context.d.ts.map +1 -0
- package/esm/src/errors/error-context.js +86 -0
- package/esm/src/errors/error-handlers.d.ts +14 -0
- package/esm/src/errors/error-handlers.d.ts.map +1 -0
- package/esm/src/errors/error-handlers.js +85 -0
- package/esm/src/errors/index.d.ts +15 -0
- package/esm/src/errors/index.d.ts.map +1 -0
- package/esm/src/errors/index.js +10 -0
- package/esm/src/errors/runtime-errors.d.ts +8 -0
- package/esm/src/errors/runtime-errors.d.ts.map +1 -0
- package/esm/src/errors/runtime-errors.js +13 -0
- package/esm/src/errors/system-errors.d.ts +21 -0
- package/esm/src/errors/system-errors.d.ts.map +1 -0
- package/esm/src/errors/system-errors.js +32 -0
- package/esm/src/errors/types.d.ts +23 -0
- package/esm/src/errors/types.d.ts.map +1 -0
- package/esm/src/errors/types.js +28 -0
- package/esm/src/errors/user-friendly/error-catalog.d.ts +8 -0
- package/esm/src/errors/user-friendly/error-catalog.d.ts.map +1 -0
- package/esm/src/errors/user-friendly/error-catalog.js +115 -0
- package/esm/src/errors/user-friendly/error-formatter.d.ts +9 -0
- package/esm/src/errors/user-friendly/error-formatter.d.ts.map +1 -0
- package/esm/src/errors/user-friendly/error-formatter.js +94 -0
- package/esm/src/errors/user-friendly/error-identifier.d.ts +2 -0
- package/esm/src/errors/user-friendly/error-identifier.d.ts.map +1 -0
- package/esm/src/errors/user-friendly/error-identifier.js +29 -0
- package/esm/src/errors/user-friendly/error-wrapper.d.ts +2 -0
- package/esm/src/errors/user-friendly/error-wrapper.d.ts.map +1 -0
- package/esm/src/errors/user-friendly/error-wrapper.js +22 -0
- package/esm/src/errors/user-friendly/index.d.ts +5 -0
- package/esm/src/errors/user-friendly/index.d.ts.map +1 -0
- package/esm/src/errors/user-friendly/index.js +4 -0
- package/esm/src/errors/veryfront-error.d.ts +135 -0
- package/esm/src/errors/veryfront-error.d.ts.map +1 -0
- package/esm/src/errors/veryfront-error.js +55 -0
- package/esm/src/html/dev-scripts.d.ts +13 -0
- package/esm/src/html/dev-scripts.d.ts.map +1 -0
- package/esm/src/html/dev-scripts.js +57 -0
- package/esm/src/html/html-detection.d.ts +2 -0
- package/esm/src/html/html-detection.d.ts.map +1 -0
- package/esm/src/html/html-detection.js +6 -0
- package/esm/src/html/html-escape.d.ts +4 -0
- package/esm/src/html/html-escape.d.ts.map +1 -0
- package/esm/src/html/html-escape.js +16 -0
- package/esm/src/html/html-injection.d.ts +20 -0
- package/esm/src/html/html-injection.d.ts.map +1 -0
- package/esm/src/html/html-injection.js +62 -0
- package/esm/src/html/html-shell-generator.d.ts +8 -0
- package/esm/src/html/html-shell-generator.d.ts.map +1 -0
- package/esm/src/html/html-shell-generator.js +258 -0
- package/esm/src/html/hydration-script-builder/dev-client-renderer.d.ts +2 -0
- package/esm/src/html/hydration-script-builder/dev-client-renderer.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/dev-client-renderer.js +17 -0
- package/esm/src/html/hydration-script-builder/dev-component-manifest.d.ts +3 -0
- package/esm/src/html/hydration-script-builder/dev-component-manifest.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/dev-component-manifest.js +8 -0
- package/esm/src/html/hydration-script-builder/dev-error-logger.d.ts +2 -0
- package/esm/src/html/hydration-script-builder/dev-error-logger.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/dev-error-logger.js +57 -0
- package/esm/src/html/hydration-script-builder/dev-scripts.d.ts +10 -0
- package/esm/src/html/hydration-script-builder/dev-scripts.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/dev-scripts.js +21 -0
- package/esm/src/html/hydration-script-builder/hydration-data-generator.d.ts +4 -0
- package/esm/src/html/hydration-script-builder/hydration-data-generator.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/hydration-data-generator.js +45 -0
- package/esm/src/html/hydration-script-builder/index.d.ts +9 -0
- package/esm/src/html/hydration-script-builder/index.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/index.js +7 -0
- package/esm/src/html/hydration-script-builder/prod-hydration.d.ts +3 -0
- package/esm/src/html/hydration-script-builder/prod-hydration.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/prod-hydration.js +28 -0
- package/esm/src/html/hydration-script-builder/prod-scripts.d.ts +2 -0
- package/esm/src/html/hydration-script-builder/prod-scripts.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/prod-scripts.js +16 -0
- package/esm/src/html/hydration-script-builder/templates/index.d.ts +5 -0
- package/esm/src/html/hydration-script-builder/templates/index.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/templates/index.js +4 -0
- package/esm/src/html/hydration-script-builder/templates/loader.d.ts +2 -0
- package/esm/src/html/hydration-script-builder/templates/loader.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/templates/loader.js +98 -0
- package/esm/src/html/hydration-script-builder/templates/renderer.d.ts +2 -0
- package/esm/src/html/hydration-script-builder/templates/renderer.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/templates/renderer.js +174 -0
- package/esm/src/html/hydration-script-builder/templates/router.d.ts +2 -0
- package/esm/src/html/hydration-script-builder/templates/router.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/templates/router.js +756 -0
- package/esm/src/html/hydration-script-builder/templates/spa-renderer.d.ts +3 -0
- package/esm/src/html/hydration-script-builder/templates/spa-renderer.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/templates/spa-renderer.js +103 -0
- package/esm/src/html/hydration-script-builder/types.d.ts +30 -0
- package/esm/src/html/hydration-script-builder/types.d.ts.map +1 -0
- package/esm/src/html/hydration-script-builder/types.js +1 -0
- package/esm/src/html/index.d.ts +15 -0
- package/esm/src/html/index.d.ts.map +1 -0
- package/esm/src/html/index.js +11 -0
- package/esm/src/html/metadata-builder.d.ts +14 -0
- package/esm/src/html/metadata-builder.d.ts.map +1 -0
- package/esm/src/html/metadata-builder.js +16 -0
- package/esm/src/html/metadata-extraction.d.ts +3 -0
- package/esm/src/html/metadata-extraction.d.ts.map +1 -0
- package/esm/src/html/metadata-extraction.js +43 -0
- package/esm/src/html/styles-builder/css-pregeneration.d.ts +58 -0
- package/esm/src/html/styles-builder/css-pregeneration.d.ts.map +1 -0
- package/esm/src/html/styles-builder/css-pregeneration.js +104 -0
- package/esm/src/html/styles-builder/dev-styles.d.ts +2 -0
- package/esm/src/html/styles-builder/dev-styles.d.ts.map +1 -0
- package/esm/src/html/styles-builder/dev-styles.js +69 -0
- package/esm/src/html/styles-builder/index.d.ts +5 -0
- package/esm/src/html/styles-builder/index.d.ts.map +1 -0
- package/esm/src/html/styles-builder/index.js +4 -0
- package/esm/src/html/styles-builder/production-styles.d.ts +2 -0
- package/esm/src/html/styles-builder/production-styles.d.ts.map +1 -0
- package/esm/src/html/styles-builder/production-styles.js +75 -0
- package/esm/src/html/styles-builder/tailwind-compiler.d.ts +80 -0
- package/esm/src/html/styles-builder/tailwind-compiler.d.ts.map +1 -0
- package/esm/src/html/styles-builder/tailwind-compiler.js +608 -0
- package/esm/src/html/styles-builder/theme-variables.d.ts +2 -0
- package/esm/src/html/styles-builder/theme-variables.d.ts.map +1 -0
- package/esm/src/html/styles-builder/theme-variables.js +78 -0
- package/esm/src/html/tag-generators.d.ts +6 -0
- package/esm/src/html/tag-generators.d.ts.map +1 -0
- package/esm/src/html/tag-generators.js +74 -0
- package/esm/src/html/types.d.ts +66 -0
- package/esm/src/html/types.d.ts.map +1 -0
- package/esm/src/html/types.js +1 -0
- package/esm/src/html/utils.d.ts +18 -0
- package/esm/src/html/utils.d.ts.map +1 -0
- package/esm/src/html/utils.js +194 -0
- package/esm/src/index.d.ts +24 -0
- package/esm/src/index.d.ts.map +1 -0
- package/esm/src/index.js +12 -0
- package/esm/src/issues/core.d.ts +94 -0
- package/esm/src/issues/core.d.ts.map +1 -0
- package/esm/src/issues/core.js +325 -0
- package/esm/src/issues/index.d.ts +6 -0
- package/esm/src/issues/index.d.ts.map +1 -0
- package/esm/src/issues/index.js +3 -0
- package/esm/src/issues/mcp.d.ts +13 -0
- package/esm/src/issues/mcp.d.ts.map +1 -0
- package/esm/src/issues/mcp.js +174 -0
- package/esm/src/issues/schema.d.ts +169 -0
- package/esm/src/issues/schema.d.ts.map +1 -0
- package/esm/src/issues/schema.js +135 -0
- package/esm/src/issues/types.d.ts +48 -0
- package/esm/src/issues/types.d.ts.map +1 -0
- package/esm/src/issues/types.js +1 -0
- package/esm/src/mcp/index.d.ts +11 -0
- package/esm/src/mcp/index.d.ts.map +1 -0
- package/esm/src/mcp/index.js +6 -0
- package/esm/src/mcp/registry.d.ts +11 -0
- package/esm/src/mcp/registry.d.ts.map +1 -0
- package/esm/src/mcp/registry.js +30 -0
- package/esm/src/mcp/server.d.ts +39 -0
- package/esm/src/mcp/server.d.ts.map +1 -0
- package/esm/src/mcp/server.js +244 -0
- package/esm/src/mcp/types.d.ts +27 -0
- package/esm/src/mcp/types.d.ts.map +1 -0
- package/esm/src/mcp/types.js +1 -0
- package/esm/src/middleware/builtin/index.d.ts +8 -0
- package/esm/src/middleware/builtin/index.d.ts.map +1 -0
- package/esm/src/middleware/builtin/index.js +6 -0
- package/esm/src/middleware/builtin/logger.d.ts +12 -0
- package/esm/src/middleware/builtin/logger.d.ts.map +1 -0
- package/esm/src/middleware/builtin/logger.js +142 -0
- package/esm/src/middleware/builtin/security/rate-limit.d.ts +19 -0
- package/esm/src/middleware/builtin/security/rate-limit.d.ts.map +1 -0
- package/esm/src/middleware/builtin/security/rate-limit.js +66 -0
- package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts +17 -0
- package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts.map +1 -0
- package/esm/src/middleware/builtin/security/redis-rate-limit.js +62 -0
- package/esm/src/middleware/builtin/security/types.d.ts +30 -0
- package/esm/src/middleware/builtin/security/types.d.ts.map +1 -0
- package/esm/src/middleware/builtin/security/types.js +1 -0
- package/esm/src/middleware/builtin/timeout.d.ts +28 -0
- package/esm/src/middleware/builtin/timeout.d.ts.map +1 -0
- package/esm/src/middleware/builtin/timeout.js +72 -0
- package/esm/src/middleware/builtin/types.d.ts +27 -0
- package/esm/src/middleware/builtin/types.d.ts.map +1 -0
- package/esm/src/middleware/builtin/types.js +3 -0
- package/esm/src/middleware/core/context.d.ts +18 -0
- package/esm/src/middleware/core/context.d.ts.map +1 -0
- package/esm/src/middleware/core/context.js +49 -0
- package/esm/src/middleware/core/index.d.ts +5 -0
- package/esm/src/middleware/core/index.d.ts.map +1 -0
- package/esm/src/middleware/core/index.js +2 -0
- package/esm/src/middleware/core/pipeline/composer.d.ts +6 -0
- package/esm/src/middleware/core/pipeline/composer.d.ts.map +1 -0
- package/esm/src/middleware/core/pipeline/composer.js +28 -0
- package/esm/src/middleware/core/pipeline/executor.d.ts +5 -0
- package/esm/src/middleware/core/pipeline/executor.d.ts.map +1 -0
- package/esm/src/middleware/core/pipeline/executor.js +40 -0
- package/esm/src/middleware/core/pipeline/index.d.ts +5 -0
- package/esm/src/middleware/core/pipeline/index.d.ts.map +1 -0
- package/esm/src/middleware/core/pipeline/index.js +3 -0
- package/esm/src/middleware/core/pipeline/pipeline.d.ts +21 -0
- package/esm/src/middleware/core/pipeline/pipeline.d.ts.map +1 -0
- package/esm/src/middleware/core/pipeline/pipeline.js +46 -0
- package/esm/src/middleware/core/pipeline/types.d.ts +2 -0
- package/esm/src/middleware/core/pipeline/types.d.ts.map +1 -0
- package/esm/src/middleware/core/pipeline/types.js +1 -0
- package/esm/src/middleware/core/types.d.ts +22 -0
- package/esm/src/middleware/core/types.d.ts.map +1 -0
- package/esm/src/middleware/core/types.js +1 -0
- package/esm/src/middleware/index.d.ts +6 -0
- package/esm/src/middleware/index.d.ts.map +1 -0
- package/esm/src/middleware/index.js +3 -0
- package/esm/src/modules/component-registry/index.d.ts +3 -0
- package/esm/src/modules/component-registry/index.d.ts.map +1 -0
- package/esm/src/modules/component-registry/index.js +1 -0
- package/esm/src/modules/component-registry/registry.d.ts +61 -0
- package/esm/src/modules/component-registry/registry.d.ts.map +1 -0
- package/esm/src/modules/component-registry/registry.js +173 -0
- package/esm/src/modules/import-map/default-import-map.d.ts +17 -0
- package/esm/src/modules/import-map/default-import-map.d.ts.map +1 -0
- package/esm/src/modules/import-map/default-import-map.js +63 -0
- package/esm/src/modules/import-map/index.d.ts +8 -0
- package/esm/src/modules/import-map/index.d.ts.map +1 -0
- package/esm/src/modules/import-map/index.js +6 -0
- package/esm/src/modules/import-map/loader.d.ts +4 -0
- package/esm/src/modules/import-map/loader.d.ts.map +1 -0
- package/esm/src/modules/import-map/loader.js +95 -0
- package/esm/src/modules/import-map/merger.d.ts +3 -0
- package/esm/src/modules/import-map/merger.d.ts.map +1 -0
- package/esm/src/modules/import-map/merger.js +15 -0
- package/esm/src/modules/import-map/preloader.d.ts +34 -0
- package/esm/src/modules/import-map/preloader.d.ts.map +1 -0
- package/esm/src/modules/import-map/preloader.js +67 -0
- package/esm/src/modules/import-map/resolver.d.ts +3 -0
- package/esm/src/modules/import-map/resolver.d.ts.map +1 -0
- package/esm/src/modules/import-map/resolver.js +84 -0
- package/esm/src/modules/import-map/transformer.d.ts +3 -0
- package/esm/src/modules/import-map/transformer.d.ts.map +1 -0
- package/esm/src/modules/import-map/transformer.js +30 -0
- package/esm/src/modules/import-map/types.d.ts +8 -0
- package/esm/src/modules/import-map/types.d.ts.map +1 -0
- package/esm/src/modules/import-map/types.js +1 -0
- package/esm/src/modules/manifest/route-module-manifest.d.ts +62 -0
- package/esm/src/modules/manifest/route-module-manifest.d.ts.map +1 -0
- package/esm/src/modules/manifest/route-module-manifest.js +171 -0
- package/esm/src/modules/react-loader/component-loader.d.ts +5 -0
- package/esm/src/modules/react-loader/component-loader.d.ts.map +1 -0
- package/esm/src/modules/react-loader/component-loader.js +48 -0
- package/esm/src/modules/react-loader/extract-component.d.ts +3 -0
- package/esm/src/modules/react-loader/extract-component.d.ts.map +1 -0
- package/esm/src/modules/react-loader/extract-component.js +14 -0
- package/esm/src/modules/react-loader/index.d.ts +7 -0
- package/esm/src/modules/react-loader/index.d.ts.map +1 -0
- package/esm/src/modules/react-loader/index.js +5 -0
- package/esm/src/modules/react-loader/path-resolver.d.ts +3 -0
- package/esm/src/modules/react-loader/path-resolver.d.ts.map +1 -0
- package/esm/src/modules/react-loader/path-resolver.js +23 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/index.d.ts +3 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/index.d.ts.map +1 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/index.js +2 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/memory.d.ts +26 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/memory.d.ts.map +1 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/memory.js +109 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts +19 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.d.ts.map +1 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/redis.js +79 -0
- package/esm/src/modules/react-loader/ssr-module-loader/concurrency/index.d.ts +9 -0
- package/esm/src/modules/react-loader/ssr-module-loader/concurrency/index.d.ts.map +1 -0
- package/esm/src/modules/react-loader/ssr-module-loader/concurrency/index.js +8 -0
- package/esm/src/modules/react-loader/ssr-module-loader/concurrency/semaphore.d.ts +10 -0
- package/esm/src/modules/react-loader/ssr-module-loader/concurrency/semaphore.d.ts.map +1 -0
- package/esm/src/modules/react-loader/ssr-module-loader/concurrency/semaphore.js +48 -0
- package/esm/src/modules/react-loader/ssr-module-loader/constants.d.ts +18 -0
- package/esm/src/modules/react-loader/ssr-module-loader/constants.d.ts.map +1 -0
- package/esm/src/modules/react-loader/ssr-module-loader/constants.js +21 -0
- package/esm/src/modules/react-loader/ssr-module-loader/index.d.ts +8 -0
- package/esm/src/modules/react-loader/ssr-module-loader/index.d.ts.map +1 -0
- package/esm/src/modules/react-loader/ssr-module-loader/index.js +14 -0
- package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts +69 -0
- package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -0
- package/esm/src/modules/react-loader/ssr-module-loader/loader.js +606 -0
- package/esm/src/modules/react-loader/ssr-module-loader/types.d.ts +32 -0
- package/esm/src/modules/react-loader/ssr-module-loader/types.d.ts.map +1 -0
- package/esm/src/modules/react-loader/ssr-module-loader/types.js +8 -0
- package/esm/src/modules/react-loader/temp-directory.d.ts +4 -0
- package/esm/src/modules/react-loader/temp-directory.d.ts.map +1 -0
- package/esm/src/modules/react-loader/temp-directory.js +48 -0
- package/esm/src/modules/react-loader/types.d.ts +18 -0
- package/esm/src/modules/react-loader/types.d.ts.map +1 -0
- package/esm/src/modules/react-loader/types.js +1 -0
- package/esm/src/modules/react-loader/unified-loader.d.ts +4 -0
- package/esm/src/modules/react-loader/unified-loader.d.ts.map +1 -0
- package/esm/src/modules/react-loader/unified-loader.js +64 -0
- package/esm/src/modules/server/api-server.d.ts +22 -0
- package/esm/src/modules/server/api-server.d.ts.map +1 -0
- package/esm/src/modules/server/api-server.js +36 -0
- package/esm/src/modules/server/index.d.ts +5 -0
- package/esm/src/modules/server/index.d.ts.map +1 -0
- package/esm/src/modules/server/index.js +4 -0
- package/esm/src/modules/server/module-batch-handler.d.ts +48 -0
- package/esm/src/modules/server/module-batch-handler.d.ts.map +1 -0
- package/esm/src/modules/server/module-batch-handler.js +298 -0
- package/esm/src/modules/server/module-server.d.ts +36 -0
- package/esm/src/modules/server/module-server.d.ts.map +1 -0
- package/esm/src/modules/server/module-server.js +440 -0
- package/esm/src/modules/server/rate-limiter.d.ts +9 -0
- package/esm/src/modules/server/rate-limiter.d.ts.map +1 -0
- package/esm/src/modules/server/rate-limiter.js +24 -0
- package/esm/src/modules/server/ssr-import-rewriter.d.ts +14 -0
- package/esm/src/modules/server/ssr-import-rewriter.d.ts.map +1 -0
- package/esm/src/modules/server/ssr-import-rewriter.js +80 -0
- package/esm/src/modules/server/websocket-handler.d.ts +6 -0
- package/esm/src/modules/server/websocket-handler.d.ts.map +1 -0
- package/esm/src/modules/server/websocket-handler.js +103 -0
- package/esm/src/oauth/handlers/callback-handler.d.ts +41 -0
- package/esm/src/oauth/handlers/callback-handler.d.ts.map +1 -0
- package/esm/src/oauth/handlers/callback-handler.js +88 -0
- package/esm/src/oauth/handlers/index.d.ts +9 -0
- package/esm/src/oauth/handlers/index.d.ts.map +1 -0
- package/esm/src/oauth/handlers/index.js +8 -0
- package/esm/src/oauth/handlers/init-handler.d.ts +28 -0
- package/esm/src/oauth/handlers/init-handler.d.ts.map +1 -0
- package/esm/src/oauth/handlers/init-handler.js +64 -0
- package/esm/src/oauth/index.d.ts +26 -0
- package/esm/src/oauth/index.d.ts.map +1 -0
- package/esm/src/oauth/index.js +24 -0
- package/esm/src/oauth/providers/atlassian.d.ts +10 -0
- package/esm/src/oauth/providers/atlassian.d.ts.map +1 -0
- package/esm/src/oauth/providers/atlassian.js +61 -0
- package/esm/src/oauth/providers/base.d.ts +37 -0
- package/esm/src/oauth/providers/base.d.ts.map +1 -0
- package/esm/src/oauth/providers/base.js +264 -0
- package/esm/src/oauth/providers/common.d.ts +56 -0
- package/esm/src/oauth/providers/common.d.ts.map +1 -0
- package/esm/src/oauth/providers/common.js +353 -0
- package/esm/src/oauth/providers/google.d.ts +12 -0
- package/esm/src/oauth/providers/google.d.ts.map +1 -0
- package/esm/src/oauth/providers/google.js +62 -0
- package/esm/src/oauth/providers/index.d.ts +8 -0
- package/esm/src/oauth/providers/index.d.ts.map +1 -0
- package/esm/src/oauth/providers/index.js +6 -0
- package/esm/src/oauth/providers/microsoft.d.ts +12 -0
- package/esm/src/oauth/providers/microsoft.d.ts.map +1 -0
- package/esm/src/oauth/providers/microsoft.js +77 -0
- package/esm/src/oauth/token-store/index.d.ts +9 -0
- package/esm/src/oauth/token-store/index.d.ts.map +1 -0
- package/esm/src/oauth/token-store/index.js +7 -0
- package/esm/src/oauth/token-store/memory.d.ts +19 -0
- package/esm/src/oauth/token-store/memory.d.ts.map +1 -0
- package/esm/src/oauth/token-store/memory.js +58 -0
- package/esm/src/oauth/types.d.ts +68 -0
- package/esm/src/oauth/types.d.ts.map +1 -0
- package/esm/src/oauth/types.js +1 -0
- package/esm/src/observability/auto-instrument/configurator.d.ts +4 -0
- package/esm/src/observability/auto-instrument/configurator.d.ts.map +1 -0
- package/esm/src/observability/auto-instrument/configurator.js +9 -0
- package/esm/src/observability/auto-instrument/http-instrumentation.d.ts +17 -0
- package/esm/src/observability/auto-instrument/http-instrumentation.d.ts.map +1 -0
- package/esm/src/observability/auto-instrument/http-instrumentation.js +170 -0
- package/esm/src/observability/auto-instrument/index.d.ts +6 -0
- package/esm/src/observability/auto-instrument/index.d.ts.map +1 -0
- package/esm/src/observability/auto-instrument/index.js +4 -0
- package/esm/src/observability/auto-instrument/orchestrator.d.ts +10 -0
- package/esm/src/observability/auto-instrument/orchestrator.d.ts.map +1 -0
- package/esm/src/observability/auto-instrument/orchestrator.js +45 -0
- package/esm/src/observability/auto-instrument/react-instrumentation.d.ts +4 -0
- package/esm/src/observability/auto-instrument/react-instrumentation.d.ts.map +1 -0
- package/esm/src/observability/auto-instrument/react-instrumentation.js +63 -0
- package/esm/src/observability/auto-instrument/types.d.ts +39 -0
- package/esm/src/observability/auto-instrument/types.d.ts.map +1 -0
- package/esm/src/observability/auto-instrument/types.js +1 -0
- package/esm/src/observability/auto-instrument/wrappers.d.ts +5 -0
- package/esm/src/observability/auto-instrument/wrappers.d.ts.map +1 -0
- package/esm/src/observability/auto-instrument/wrappers.js +90 -0
- package/esm/src/observability/index.d.ts +5 -0
- package/esm/src/observability/index.d.ts.map +1 -0
- package/esm/src/observability/index.js +4 -0
- package/esm/src/observability/instruments/build-instruments.d.ts +15 -0
- package/esm/src/observability/instruments/build-instruments.d.ts.map +1 -0
- package/esm/src/observability/instruments/build-instruments.js +25 -0
- package/esm/src/observability/instruments/cache-instruments.d.ts +12 -0
- package/esm/src/observability/instruments/cache-instruments.d.ts.map +1 -0
- package/esm/src/observability/instruments/cache-instruments.js +38 -0
- package/esm/src/observability/instruments/data-instruments.d.ts +9 -0
- package/esm/src/observability/instruments/data-instruments.d.ts.map +1 -0
- package/esm/src/observability/instruments/data-instruments.js +18 -0
- package/esm/src/observability/instruments/http-instruments.d.ts +9 -0
- package/esm/src/observability/instruments/http-instruments.d.ts.map +1 -0
- package/esm/src/observability/instruments/http-instruments.js +18 -0
- package/esm/src/observability/instruments/index.d.ts +9 -0
- package/esm/src/observability/instruments/index.d.ts.map +1 -0
- package/esm/src/observability/instruments/index.js +1 -0
- package/esm/src/observability/instruments/instruments-factory.d.ts +4 -0
- package/esm/src/observability/instruments/instruments-factory.d.ts.map +1 -0
- package/esm/src/observability/instruments/instruments-factory.js +50 -0
- package/esm/src/observability/instruments/memory-instruments.d.ts +10 -0
- package/esm/src/observability/instruments/memory-instruments.d.ts.map +1 -0
- package/esm/src/observability/instruments/memory-instruments.js +49 -0
- package/esm/src/observability/instruments/render-instruments.d.ts +9 -0
- package/esm/src/observability/instruments/render-instruments.d.ts.map +1 -0
- package/esm/src/observability/instruments/render-instruments.js +18 -0
- package/esm/src/observability/instruments/rsc-instruments.d.ts +13 -0
- package/esm/src/observability/instruments/rsc-instruments.d.ts.map +1 -0
- package/esm/src/observability/instruments/rsc-instruments.js +35 -0
- package/esm/src/observability/metrics/config.d.ts +10 -0
- package/esm/src/observability/metrics/config.d.ts.map +1 -0
- package/esm/src/observability/metrics/config.js +69 -0
- package/esm/src/observability/metrics/index.d.ts +30 -0
- package/esm/src/observability/metrics/index.d.ts.map +1 -0
- package/esm/src/observability/metrics/index.js +74 -0
- package/esm/src/observability/metrics/manager.d.ts +31 -0
- package/esm/src/observability/metrics/manager.d.ts.map +1 -0
- package/esm/src/observability/metrics/manager.js +108 -0
- package/esm/src/observability/metrics/recorder.d.ts +28 -0
- package/esm/src/observability/metrics/recorder.d.ts.map +1 -0
- package/esm/src/observability/metrics/recorder.js +92 -0
- package/esm/src/observability/metrics/types.d.ts +61 -0
- package/esm/src/observability/metrics/types.d.ts.map +1 -0
- package/esm/src/observability/metrics/types.js +5 -0
- package/esm/src/observability/simple-metrics/index.d.ts +25 -0
- package/esm/src/observability/simple-metrics/index.d.ts.map +1 -0
- package/esm/src/observability/simple-metrics/index.js +23 -0
- package/esm/src/observability/simple-metrics/metrics-recorder.d.ts +114 -0
- package/esm/src/observability/simple-metrics/metrics-recorder.d.ts.map +1 -0
- package/esm/src/observability/simple-metrics/metrics-recorder.js +227 -0
- package/esm/src/observability/simple-metrics/metrics-state.d.ts +7 -0
- package/esm/src/observability/simple-metrics/metrics-state.d.ts.map +1 -0
- package/esm/src/observability/simple-metrics/metrics-state.js +89 -0
- package/esm/src/observability/simple-metrics/observability-loader.d.ts +22 -0
- package/esm/src/observability/simple-metrics/observability-loader.d.ts.map +1 -0
- package/esm/src/observability/simple-metrics/observability-loader.js +47 -0
- package/esm/src/observability/simple-metrics/otel-instruments.d.ts +11 -0
- package/esm/src/observability/simple-metrics/otel-instruments.d.ts.map +1 -0
- package/esm/src/observability/simple-metrics/otel-instruments.js +77 -0
- package/esm/src/observability/simple-metrics/types.d.ts +61 -0
- package/esm/src/observability/simple-metrics/types.d.ts.map +1 -0
- package/esm/src/observability/simple-metrics/types.js +5 -0
- package/esm/src/observability/tracing/config.d.ts +4 -0
- package/esm/src/observability/tracing/config.d.ts.map +1 -0
- package/esm/src/observability/tracing/config.js +54 -0
- package/esm/src/observability/tracing/context-propagation.d.ts +14 -0
- package/esm/src/observability/tracing/context-propagation.d.ts.map +1 -0
- package/esm/src/observability/tracing/context-propagation.js +69 -0
- package/esm/src/observability/tracing/index.d.ts +28 -0
- package/esm/src/observability/tracing/index.d.ts.map +1 -0
- package/esm/src/observability/tracing/index.js +72 -0
- package/esm/src/observability/tracing/manager.d.ts +23 -0
- package/esm/src/observability/tracing/manager.d.ts.map +1 -0
- package/esm/src/observability/tracing/manager.js +82 -0
- package/esm/src/observability/tracing/otlp-setup.d.ts +74 -0
- package/esm/src/observability/tracing/otlp-setup.d.ts.map +1 -0
- package/esm/src/observability/tracing/otlp-setup.js +273 -0
- package/esm/src/observability/tracing/span-names.d.ts +90 -0
- package/esm/src/observability/tracing/span-names.d.ts.map +1 -0
- package/esm/src/observability/tracing/span-names.js +89 -0
- package/esm/src/observability/tracing/span-operations.d.ts +13 -0
- package/esm/src/observability/tracing/span-operations.d.ts.map +1 -0
- package/esm/src/observability/tracing/span-operations.js +86 -0
- package/esm/src/observability/tracing/types.d.ts +43 -0
- package/esm/src/observability/tracing/types.d.ts.map +1 -0
- package/esm/src/observability/tracing/types.js +1 -0
- package/esm/src/platform/adapters/base.d.ts +156 -0
- package/esm/src/platform/adapters/base.d.ts.map +1 -0
- package/esm/src/platform/adapters/base.js +1 -0
- package/esm/src/platform/adapters/bun.d.ts +3 -0
- package/esm/src/platform/adapters/bun.d.ts.map +1 -0
- package/esm/src/platform/adapters/bun.js +1 -0
- package/esm/src/platform/adapters/deno.d.ts +2 -0
- package/esm/src/platform/adapters/deno.d.ts.map +1 -0
- package/esm/src/platform/adapters/deno.js +1 -0
- package/esm/src/platform/adapters/detect.d.ts +29 -0
- package/esm/src/platform/adapters/detect.d.ts.map +1 -0
- package/esm/src/platform/adapters/detect.js +57 -0
- package/esm/src/platform/adapters/fallback-wrapper.d.ts +21 -0
- package/esm/src/platform/adapters/fallback-wrapper.d.ts.map +1 -0
- package/esm/src/platform/adapters/fallback-wrapper.js +87 -0
- package/esm/src/platform/adapters/fs/cache/file-cache.d.ts +78 -0
- package/esm/src/platform/adapters/fs/cache/file-cache.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/cache/file-cache.js +356 -0
- package/esm/src/platform/adapters/fs/cache/size-estimator.d.ts +2 -0
- package/esm/src/platform/adapters/fs/cache/size-estimator.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/cache/size-estimator.js +9 -0
- package/esm/src/platform/adapters/fs/cache/types.d.ts +19 -0
- package/esm/src/platform/adapters/fs/cache/types.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/cache/types.js +1 -0
- package/esm/src/platform/adapters/fs/factory.d.ts +3 -0
- package/esm/src/platform/adapters/fs/factory.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/factory.js +72 -0
- package/esm/src/platform/adapters/fs/github/adapter.d.ts +38 -0
- package/esm/src/platform/adapters/fs/github/adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/github/adapter.js +107 -0
- package/esm/src/platform/adapters/fs/github/directory-operations.d.ts +14 -0
- package/esm/src/platform/adapters/fs/github/directory-operations.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/github/directory-operations.js +71 -0
- package/esm/src/platform/adapters/fs/github/github-api-client.d.ts +28 -0
- package/esm/src/platform/adapters/fs/github/github-api-client.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/github/github-api-client.js +158 -0
- package/esm/src/platform/adapters/fs/github/index.d.ts +9 -0
- package/esm/src/platform/adapters/fs/github/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/github/index.js +7 -0
- package/esm/src/platform/adapters/fs/github/read-operations.d.ts +23 -0
- package/esm/src/platform/adapters/fs/github/read-operations.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/github/read-operations.js +137 -0
- package/esm/src/platform/adapters/fs/github/schemas.d.ts +318 -0
- package/esm/src/platform/adapters/fs/github/schemas.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/github/schemas.js +67 -0
- package/esm/src/platform/adapters/fs/github/stat-operations.d.ts +31 -0
- package/esm/src/platform/adapters/fs/github/stat-operations.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/github/stat-operations.js +225 -0
- package/esm/src/platform/adapters/fs/github/types.d.ts +56 -0
- package/esm/src/platform/adapters/fs/github/types.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/github/types.js +39 -0
- package/esm/src/platform/adapters/fs/index.d.ts +12 -0
- package/esm/src/platform/adapters/fs/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/index.js +8 -0
- package/esm/src/platform/adapters/fs/integration.d.ts +15 -0
- package/esm/src/platform/adapters/fs/integration.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/integration.js +60 -0
- package/esm/src/platform/adapters/fs/shared-types.d.ts +12 -0
- package/esm/src/platform/adapters/fs/shared-types.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/shared-types.js +5 -0
- package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts +98 -0
- package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/adapter.js +1099 -0
- package/esm/src/platform/adapters/fs/veryfront/cache-keys.d.ts +6 -0
- package/esm/src/platform/adapters/fs/veryfront/cache-keys.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/cache-keys.js +19 -0
- package/esm/src/platform/adapters/fs/veryfront/directory-operations.d.ts +20 -0
- package/esm/src/platform/adapters/fs/veryfront/directory-operations.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/directory-operations.js +178 -0
- package/esm/src/platform/adapters/fs/veryfront/index.d.ts +3 -0
- package/esm/src/platform/adapters/fs/veryfront/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/index.js +1 -0
- package/esm/src/platform/adapters/fs/veryfront/invalidation-state.d.ts +63 -0
- package/esm/src/platform/adapters/fs/veryfront/invalidation-state.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/invalidation-state.js +145 -0
- package/esm/src/platform/adapters/fs/veryfront/multi-project-adapter.d.ts +58 -0
- package/esm/src/platform/adapters/fs/veryfront/multi-project-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/multi-project-adapter.js +189 -0
- package/esm/src/platform/adapters/fs/veryfront/path-normalizer.d.ts +7 -0
- package/esm/src/platform/adapters/fs/veryfront/path-normalizer.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/path-normalizer.js +32 -0
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.d.ts +31 -0
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.js +337 -0
- package/esm/src/platform/adapters/fs/veryfront/read-operations.d.ts +52 -0
- package/esm/src/platform/adapters/fs/veryfront/read-operations.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/read-operations.js +386 -0
- package/esm/src/platform/adapters/fs/veryfront/stat-operations.d.ts +29 -0
- package/esm/src/platform/adapters/fs/veryfront/stat-operations.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/stat-operations.js +380 -0
- package/esm/src/platform/adapters/fs/veryfront/types.d.ts +151 -0
- package/esm/src/platform/adapters/fs/veryfront/types.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/types.js +34 -0
- package/esm/src/platform/adapters/fs/wrapper.d.ts +70 -0
- package/esm/src/platform/adapters/fs/wrapper.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/wrapper.js +179 -0
- package/esm/src/platform/adapters/index.d.ts +15 -0
- package/esm/src/platform/adapters/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/index.js +13 -0
- package/esm/src/platform/adapters/mock.d.ts +9 -0
- package/esm/src/platform/adapters/mock.d.ts.map +1 -0
- package/esm/src/platform/adapters/mock.js +163 -0
- package/esm/src/platform/adapters/node.d.ts +3 -0
- package/esm/src/platform/adapters/node.d.ts.map +1 -0
- package/esm/src/platform/adapters/node.js +1 -0
- package/esm/src/platform/adapters/redis/deno.d.ts +48 -0
- package/esm/src/platform/adapters/redis/deno.d.ts.map +1 -0
- package/esm/src/platform/adapters/redis/deno.js +89 -0
- package/esm/src/platform/adapters/redis/index.d.ts +15 -0
- package/esm/src/platform/adapters/redis/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/redis/index.js +12 -0
- package/esm/src/platform/adapters/redis/interface.d.ts +48 -0
- package/esm/src/platform/adapters/redis/interface.d.ts.map +1 -0
- package/esm/src/platform/adapters/redis/interface.js +1 -0
- package/esm/src/platform/adapters/redis/modules.d.ts +7 -0
- package/esm/src/platform/adapters/redis/modules.d.ts.map +1 -0
- package/esm/src/platform/adapters/redis/modules.js +34 -0
- package/esm/src/platform/adapters/redis/node.d.ts +58 -0
- package/esm/src/platform/adapters/redis/node.d.ts.map +1 -0
- package/esm/src/platform/adapters/redis/node.js +100 -0
- package/esm/src/platform/adapters/redis/types.d.ts +102 -0
- package/esm/src/platform/adapters/redis/types.d.ts.map +1 -0
- package/esm/src/platform/adapters/redis/types.js +1 -0
- package/esm/src/platform/adapters/redis/utils.d.ts +13 -0
- package/esm/src/platform/adapters/redis/utils.d.ts.map +1 -0
- package/esm/src/platform/adapters/redis/utils.js +24 -0
- package/esm/src/platform/adapters/registry.d.ts +23 -0
- package/esm/src/platform/adapters/registry.d.ts.map +1 -0
- package/esm/src/platform/adapters/registry.js +143 -0
- package/esm/src/platform/adapters/runtime/bun/adapter.d.ts +20 -0
- package/esm/src/platform/adapters/runtime/bun/adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/bun/adapter.js +38 -0
- package/esm/src/platform/adapters/runtime/bun/environment-adapter.d.ts +7 -0
- package/esm/src/platform/adapters/runtime/bun/environment-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/bun/environment-adapter.js +17 -0
- package/esm/src/platform/adapters/runtime/bun/filesystem-adapter.d.ts +18 -0
- package/esm/src/platform/adapters/runtime/bun/filesystem-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/bun/filesystem-adapter.js +141 -0
- package/esm/src/platform/adapters/runtime/bun/http-server.d.ts +16 -0
- package/esm/src/platform/adapters/runtime/bun/http-server.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/bun/http-server.js +38 -0
- package/esm/src/platform/adapters/runtime/bun/index.d.ts +7 -0
- package/esm/src/platform/adapters/runtime/bun/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/bun/index.js +5 -0
- package/esm/src/platform/adapters/runtime/bun/types.d.ts +40 -0
- package/esm/src/platform/adapters/runtime/bun/types.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/bun/types.js +1 -0
- package/esm/src/platform/adapters/runtime/bun/websocket-adapter.d.ts +19 -0
- package/esm/src/platform/adapters/runtime/bun/websocket-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/bun/websocket-adapter.js +38 -0
- package/esm/src/platform/adapters/runtime/deno/adapter.d.ts +59 -0
- package/esm/src/platform/adapters/runtime/deno/adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/deno/adapter.js +343 -0
- package/esm/src/platform/adapters/runtime/deno/index.d.ts +2 -0
- package/esm/src/platform/adapters/runtime/deno/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/deno/index.js +1 -0
- package/esm/src/platform/adapters/runtime/node/adapter.d.ts +20 -0
- package/esm/src/platform/adapters/runtime/node/adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/node/adapter.js +38 -0
- package/esm/src/platform/adapters/runtime/node/environment-adapter.d.ts +7 -0
- package/esm/src/platform/adapters/runtime/node/environment-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/node/environment-adapter.js +17 -0
- package/esm/src/platform/adapters/runtime/node/filesystem-adapter.d.ts +18 -0
- package/esm/src/platform/adapters/runtime/node/filesystem-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/node/filesystem-adapter.js +106 -0
- package/esm/src/platform/adapters/runtime/node/http-server.d.ts +17 -0
- package/esm/src/platform/adapters/runtime/node/http-server.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/node/http-server.js +132 -0
- package/esm/src/platform/adapters/runtime/node/index.d.ts +7 -0
- package/esm/src/platform/adapters/runtime/node/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/node/index.js +5 -0
- package/esm/src/platform/adapters/runtime/node/types.d.ts +33 -0
- package/esm/src/platform/adapters/runtime/node/types.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/node/types.js +1 -0
- package/esm/src/platform/adapters/runtime/node/websocket-adapter.d.ts +27 -0
- package/esm/src/platform/adapters/runtime/node/websocket-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/node/websocket-adapter.js +133 -0
- package/esm/src/platform/adapters/runtime/shared/node-based-shell-adapter.d.ts +9 -0
- package/esm/src/platform/adapters/runtime/shared/node-based-shell-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/shared/node-based-shell-adapter.js +27 -0
- package/esm/src/platform/adapters/runtime/shared/shared-watcher.d.ts +14 -0
- package/esm/src/platform/adapters/runtime/shared/shared-watcher.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/shared/shared-watcher.js +31 -0
- package/esm/src/platform/adapters/runtime/shared/watcher-queue.d.ts +5 -0
- package/esm/src/platform/adapters/runtime/shared/watcher-queue.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime/shared/watcher-queue.js +44 -0
- package/esm/src/platform/adapters/runtime-detection.d.ts +3 -0
- package/esm/src/platform/adapters/runtime-detection.d.ts.map +1 -0
- package/esm/src/platform/adapters/runtime-detection.js +12 -0
- package/esm/src/platform/adapters/security/index.d.ts +3 -0
- package/esm/src/platform/adapters/security/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/security/index.js +2 -0
- package/esm/src/platform/adapters/token/factory.d.ts +10 -0
- package/esm/src/platform/adapters/token/factory.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/factory.js +32 -0
- package/esm/src/platform/adapters/token/index.d.ts +4 -0
- package/esm/src/platform/adapters/token/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/index.js +3 -0
- package/esm/src/platform/adapters/token/integration.d.ts +6 -0
- package/esm/src/platform/adapters/token/integration.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/integration.js +46 -0
- package/esm/src/platform/adapters/token/veryfront/adapter.d.ts +20 -0
- package/esm/src/platform/adapters/token/veryfront/adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/adapter.js +60 -0
- package/esm/src/platform/adapters/token/veryfront/api-client.d.ts +14 -0
- package/esm/src/platform/adapters/token/veryfront/api-client.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/api-client.js +162 -0
- package/esm/src/platform/adapters/token/veryfront/index.d.ts +5 -0
- package/esm/src/platform/adapters/token/veryfront/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/index.js +4 -0
- package/esm/src/platform/adapters/token/veryfront/memory-adapter.d.ts +14 -0
- package/esm/src/platform/adapters/token/veryfront/memory-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/memory-adapter.js +47 -0
- package/esm/src/platform/adapters/token/veryfront/types.d.ts +75 -0
- package/esm/src/platform/adapters/token/veryfront/types.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/types.js +57 -0
- package/esm/src/platform/adapters/veryfront-api-client/client.d.ts +164 -0
- package/esm/src/platform/adapters/veryfront-api-client/client.d.ts.map +1 -0
- package/esm/src/platform/adapters/veryfront-api-client/client.js +371 -0
- package/esm/src/platform/adapters/veryfront-api-client/index.d.ts +6 -0
- package/esm/src/platform/adapters/veryfront-api-client/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/veryfront-api-client/index.js +5 -0
- package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts +58 -0
- package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts.map +1 -0
- package/esm/src/platform/adapters/veryfront-api-client/operations.js +293 -0
- package/esm/src/platform/adapters/veryfront-api-client/retry-handler.d.ts +10 -0
- package/esm/src/platform/adapters/veryfront-api-client/retry-handler.d.ts.map +1 -0
- package/esm/src/platform/adapters/veryfront-api-client/retry-handler.js +76 -0
- package/esm/src/platform/adapters/veryfront-api-client/schemas.d.ts +3416 -0
- package/esm/src/platform/adapters/veryfront-api-client/schemas.d.ts.map +1 -0
- package/esm/src/platform/adapters/veryfront-api-client/schemas.js +182 -0
- package/esm/src/platform/adapters/veryfront-api-client/types.d.ts +28 -0
- package/esm/src/platform/adapters/veryfront-api-client/types.d.ts.map +1 -0
- package/esm/src/platform/adapters/veryfront-api-client/types.js +15 -0
- package/esm/src/platform/compat/console/deno.d.ts +8 -0
- package/esm/src/platform/compat/console/deno.d.ts.map +1 -0
- package/esm/src/platform/compat/console/deno.js +21 -0
- package/esm/src/platform/compat/console/index.d.ts +41 -0
- package/esm/src/platform/compat/console/index.d.ts.map +1 -0
- package/esm/src/platform/compat/console/index.js +75 -0
- package/esm/src/platform/compat/console/node.d.ts +18 -0
- package/esm/src/platform/compat/console/node.d.ts.map +1 -0
- package/esm/src/platform/compat/console/node.js +45 -0
- package/esm/src/platform/compat/console/types.d.ts +18 -0
- package/esm/src/platform/compat/console/types.d.ts.map +1 -0
- package/esm/src/platform/compat/console/types.js +1 -0
- package/esm/src/platform/compat/crypto.d.ts +8 -0
- package/esm/src/platform/compat/crypto.d.ts.map +1 -0
- package/esm/src/platform/compat/crypto.js +21 -0
- package/esm/src/platform/compat/flags.d.ts +16 -0
- package/esm/src/platform/compat/flags.d.ts.map +1 -0
- package/esm/src/platform/compat/flags.js +33 -0
- package/esm/src/platform/compat/fs.d.ts +50 -0
- package/esm/src/platform/compat/fs.d.ts.map +1 -0
- package/esm/src/platform/compat/fs.js +228 -0
- package/esm/src/platform/compat/http/deno-server.d.ts +7 -0
- package/esm/src/platform/compat/http/deno-server.d.ts.map +1 -0
- package/esm/src/platform/compat/http/deno-server.js +16 -0
- package/esm/src/platform/compat/http/factory.d.ts +3 -0
- package/esm/src/platform/compat/http/factory.d.ts.map +1 -0
- package/esm/src/platform/compat/http/factory.js +6 -0
- package/esm/src/platform/compat/http/index.d.ts +9 -0
- package/esm/src/platform/compat/http/index.d.ts.map +1 -0
- package/esm/src/platform/compat/http/index.js +6 -0
- package/esm/src/platform/compat/http/node-server.d.ts +10 -0
- package/esm/src/platform/compat/http/node-server.d.ts.map +1 -0
- package/esm/src/platform/compat/http/node-server.js +71 -0
- package/esm/src/platform/compat/http/node-types.d.ts +29 -0
- package/esm/src/platform/compat/http/node-types.d.ts.map +1 -0
- package/esm/src/platform/compat/http/node-types.js +1 -0
- package/esm/src/platform/compat/http/request-adapter.d.ts +4 -0
- package/esm/src/platform/compat/http/request-adapter.d.ts.map +1 -0
- package/esm/src/platform/compat/http/request-adapter.js +9 -0
- package/esm/src/platform/compat/http/responses.d.ts +44 -0
- package/esm/src/platform/compat/http/responses.d.ts.map +1 -0
- package/esm/src/platform/compat/http/responses.js +161 -0
- package/esm/src/platform/compat/http/types.d.ts +24 -0
- package/esm/src/platform/compat/http/types.d.ts.map +1 -0
- package/esm/src/platform/compat/http/types.js +1 -0
- package/esm/src/platform/compat/http/websocket.d.ts +5 -0
- package/esm/src/platform/compat/http/websocket.d.ts.map +1 -0
- package/esm/src/platform/compat/http/websocket.js +19 -0
- package/esm/src/platform/compat/index.d.ts +10 -0
- package/esm/src/platform/compat/index.d.ts.map +1 -0
- package/esm/src/platform/compat/index.js +9 -0
- package/esm/src/platform/compat/kv/factory.d.ts +7 -0
- package/esm/src/platform/compat/kv/factory.d.ts.map +1 -0
- package/esm/src/platform/compat/kv/factory.js +38 -0
- package/esm/src/platform/compat/kv/index.d.ts +5 -0
- package/esm/src/platform/compat/kv/index.d.ts.map +1 -0
- package/esm/src/platform/compat/kv/index.js +3 -0
- package/esm/src/platform/compat/kv/memory-adapter.d.ts +15 -0
- package/esm/src/platform/compat/kv/memory-adapter.d.ts.map +1 -0
- package/esm/src/platform/compat/kv/memory-adapter.js +56 -0
- package/esm/src/platform/compat/kv/sqlite-adapter.d.ts +17 -0
- package/esm/src/platform/compat/kv/sqlite-adapter.d.ts.map +1 -0
- package/esm/src/platform/compat/kv/sqlite-adapter.js +90 -0
- package/esm/src/platform/compat/kv/types.d.ts +32 -0
- package/esm/src/platform/compat/kv/types.d.ts.map +1 -0
- package/esm/src/platform/compat/kv/types.js +1 -0
- package/esm/src/platform/compat/media-types.d.ts +5 -0
- package/esm/src/platform/compat/media-types.d.ts.map +1 -0
- package/esm/src/platform/compat/media-types.js +19 -0
- package/esm/src/platform/compat/path/basic-operations.d.ts +5 -0
- package/esm/src/platform/compat/path/basic-operations.d.ts.map +1 -0
- package/esm/src/platform/compat/path/basic-operations.js +48 -0
- package/esm/src/platform/compat/path/index.d.ts +8 -0
- package/esm/src/platform/compat/path/index.d.ts.map +1 -0
- package/esm/src/platform/compat/path/index.js +6 -0
- package/esm/src/platform/compat/path/parse-format.d.ts +4 -0
- package/esm/src/platform/compat/path/parse-format.d.ts.map +1 -0
- package/esm/src/platform/compat/path/parse-format.js +29 -0
- package/esm/src/platform/compat/path/resolution.d.ts +5 -0
- package/esm/src/platform/compat/path/resolution.d.ts.map +1 -0
- package/esm/src/platform/compat/path/resolution.js +76 -0
- package/esm/src/platform/compat/path/runtime.d.ts +8 -0
- package/esm/src/platform/compat/path/runtime.d.ts.map +1 -0
- package/esm/src/platform/compat/path/runtime.js +19 -0
- package/esm/src/platform/compat/path/security.d.ts +2 -0
- package/esm/src/platform/compat/path/security.d.ts.map +1 -0
- package/esm/src/platform/compat/path/security.js +22 -0
- package/esm/src/platform/compat/path/types.d.ts +22 -0
- package/esm/src/platform/compat/path/types.d.ts.map +1 -0
- package/esm/src/platform/compat/path/types.js +1 -0
- package/esm/src/platform/compat/path/url-conversion.d.ts +3 -0
- package/esm/src/platform/compat/path/url-conversion.d.ts.map +1 -0
- package/esm/src/platform/compat/path/url-conversion.js +45 -0
- package/esm/src/platform/compat/path-helper.d.ts +11 -0
- package/esm/src/platform/compat/path-helper.d.ts.map +1 -0
- package/esm/src/platform/compat/path-helper.js +10 -0
- package/esm/src/platform/compat/process.d.ts +147 -0
- package/esm/src/platform/compat/process.d.ts.map +1 -0
- package/esm/src/platform/compat/process.js +479 -0
- package/esm/src/platform/compat/react-paths.d.ts +14 -0
- package/esm/src/platform/compat/react-paths.d.ts.map +1 -0
- package/esm/src/platform/compat/react-paths.js +81 -0
- package/esm/src/platform/compat/runtime.d.ts +15 -0
- package/esm/src/platform/compat/runtime.d.ts.map +1 -0
- package/esm/src/platform/compat/runtime.js +36 -0
- package/esm/src/platform/compat/shims/std-path.d.ts +7 -0
- package/esm/src/platform/compat/shims/std-path.d.ts.map +1 -0
- package/esm/src/platform/compat/shims/std-path.js +11 -0
- package/esm/src/platform/compat/std/front-matter-yaml.d.ts +14 -0
- package/esm/src/platform/compat/std/front-matter-yaml.d.ts.map +1 -0
- package/esm/src/platform/compat/std/front-matter-yaml.js +23 -0
- package/esm/src/platform/compat/std/fs.d.ts +30 -0
- package/esm/src/platform/compat/std/fs.d.ts.map +1 -0
- package/esm/src/platform/compat/std/fs.js +122 -0
- package/esm/src/platform/compat/stdin.d.ts +31 -0
- package/esm/src/platform/compat/stdin.d.ts.map +1 -0
- package/esm/src/platform/compat/stdin.js +170 -0
- package/esm/src/platform/core-platform.d.ts +27 -0
- package/esm/src/platform/core-platform.d.ts.map +1 -0
- package/esm/src/platform/core-platform.js +119 -0
- package/esm/src/platform/index.d.ts +4 -0
- package/esm/src/platform/index.d.ts.map +1 -0
- package/esm/src/platform/index.js +3 -0
- package/esm/src/prompt/factory.d.ts +3 -0
- package/esm/src/prompt/factory.d.ts.map +1 -0
- package/esm/src/prompt/factory.js +29 -0
- package/esm/src/prompt/index.d.ts +5 -0
- package/esm/src/prompt/index.d.ts.map +1 -0
- package/esm/src/prompt/index.js +3 -0
- package/esm/src/prompt/registry.d.ts +14 -0
- package/esm/src/prompt/registry.d.ts.map +1 -0
- package/esm/src/prompt/registry.js +42 -0
- package/esm/src/prompt/types.d.ts +12 -0
- package/esm/src/prompt/types.d.ts.map +1 -0
- package/esm/src/prompt/types.js +1 -0
- package/esm/src/provider/adapters/ai-sdk.d.ts +27 -0
- package/esm/src/provider/adapters/ai-sdk.d.ts.map +1 -0
- package/esm/src/provider/adapters/ai-sdk.js +47 -0
- package/esm/src/provider/adapters/index.d.ts +3 -0
- package/esm/src/provider/adapters/index.d.ts.map +1 -0
- package/esm/src/provider/adapters/index.js +2 -0
- package/esm/src/provider/anthropic.d.ts +41 -0
- package/esm/src/provider/anthropic.d.ts.map +1 -0
- package/esm/src/provider/anthropic.js +252 -0
- package/esm/src/provider/base.d.ts +17 -0
- package/esm/src/provider/base.d.ts.map +1 -0
- package/esm/src/provider/base.js +225 -0
- package/esm/src/provider/factory.d.ts +27 -0
- package/esm/src/provider/factory.d.ts.map +1 -0
- package/esm/src/provider/factory.js +122 -0
- package/esm/src/provider/google.d.ts +14 -0
- package/esm/src/provider/google.d.ts.map +1 -0
- package/esm/src/provider/google.js +105 -0
- package/esm/src/provider/index.d.ts +8 -0
- package/esm/src/provider/index.d.ts.map +1 -0
- package/esm/src/provider/index.js +6 -0
- package/esm/src/provider/openai.d.ts +12 -0
- package/esm/src/provider/openai.d.ts.map +1 -0
- package/esm/src/provider/openai.js +128 -0
- package/esm/src/provider/types.d.ts +67 -0
- package/esm/src/provider/types.d.ts.map +1 -0
- package/esm/src/provider/types.js +1 -0
- package/esm/src/react/compat/config-generator.d.ts +21 -0
- package/esm/src/react/compat/config-generator.d.ts.map +1 -0
- package/esm/src/react/compat/config-generator.js +123 -0
- package/esm/src/react/compat/hooks-adapter.d.ts +47 -0
- package/esm/src/react/compat/hooks-adapter.d.ts.map +1 -0
- package/esm/src/react/compat/hooks-adapter.js +151 -0
- package/esm/src/react/compat/index.d.ts +5 -0
- package/esm/src/react/compat/index.d.ts.map +1 -0
- package/esm/src/react/compat/index.js +4 -0
- package/esm/src/react/compat/ssr-adapter/html-wrapper.d.ts +3 -0
- package/esm/src/react/compat/ssr-adapter/html-wrapper.d.ts.map +1 -0
- package/esm/src/react/compat/ssr-adapter/html-wrapper.js +33 -0
- package/esm/src/react/compat/ssr-adapter/index.d.ts +8 -0
- package/esm/src/react/compat/ssr-adapter/index.d.ts.map +1 -0
- package/esm/src/react/compat/ssr-adapter/index.js +5 -0
- package/esm/src/react/compat/ssr-adapter/response-builder.d.ts +5 -0
- package/esm/src/react/compat/ssr-adapter/response-builder.d.ts.map +1 -0
- package/esm/src/react/compat/ssr-adapter/response-builder.js +34 -0
- package/esm/src/react/compat/ssr-adapter/server-loader.d.ts +10 -0
- package/esm/src/react/compat/ssr-adapter/server-loader.d.ts.map +1 -0
- package/esm/src/react/compat/ssr-adapter/server-loader.js +39 -0
- package/esm/src/react/compat/ssr-adapter/stream-renderer.d.ts +4 -0
- package/esm/src/react/compat/ssr-adapter/stream-renderer.d.ts.map +1 -0
- package/esm/src/react/compat/ssr-adapter/stream-renderer.js +194 -0
- package/esm/src/react/compat/ssr-adapter/string-renderer.d.ts +5 -0
- package/esm/src/react/compat/ssr-adapter/string-renderer.d.ts.map +1 -0
- package/esm/src/react/compat/ssr-adapter/string-renderer.js +53 -0
- package/esm/src/react/compat/ssr-adapter/types.d.ts +47 -0
- package/esm/src/react/compat/ssr-adapter/types.d.ts.map +1 -0
- package/esm/src/react/compat/ssr-adapter/types.js +1 -0
- package/esm/src/react/compat/version-detector/compatibility-checker.d.ts +4 -0
- package/esm/src/react/compat/version-detector/compatibility-checker.d.ts.map +1 -0
- package/esm/src/react/compat/version-detector/compatibility-checker.js +46 -0
- package/esm/src/react/compat/version-detector/feature-detector.d.ts +5 -0
- package/esm/src/react/compat/version-detector/feature-detector.d.ts.map +1 -0
- package/esm/src/react/compat/version-detector/feature-detector.js +75 -0
- package/esm/src/react/compat/version-detector/index.d.ts +6 -0
- package/esm/src/react/compat/version-detector/index.d.ts.map +1 -0
- package/esm/src/react/compat/version-detector/index.js +4 -0
- package/esm/src/react/compat/version-detector/types.d.ts +39 -0
- package/esm/src/react/compat/version-detector/types.d.ts.map +1 -0
- package/esm/src/react/compat/version-detector/types.js +1 -0
- package/esm/src/react/compat/version-detector/version-cache.d.ts +7 -0
- package/esm/src/react/compat/version-detector/version-cache.d.ts.map +1 -0
- package/esm/src/react/compat/version-detector/version-cache.js +25 -0
- package/esm/src/react/compat/version-detector/version-parser.d.ts +6 -0
- package/esm/src/react/compat/version-detector/version-parser.d.ts.map +1 -0
- package/esm/src/react/compat/version-detector/version-parser.js +27 -0
- package/esm/src/react/components/AppWrapper.d.ts +14 -0
- package/esm/src/react/components/AppWrapper.d.ts.map +1 -0
- package/esm/src/react/components/AppWrapper.js +16 -0
- package/esm/src/react/components/Head.d.ts +7 -0
- package/esm/src/react/components/Head.d.ts.map +1 -0
- package/esm/src/react/components/Head.js +98 -0
- package/esm/src/react/components/LayoutComponent.d.ts +11 -0
- package/esm/src/react/components/LayoutComponent.d.ts.map +1 -0
- package/esm/src/react/components/LayoutComponent.js +41 -0
- package/esm/src/react/components/Link.d.ts +6 -0
- package/esm/src/react/components/Link.d.ts.map +1 -0
- package/esm/src/react/components/Link.js +4 -0
- package/esm/src/react/components/MDXProvider.d.ts +9 -0
- package/esm/src/react/components/MDXProvider.d.ts.map +1 -0
- package/esm/src/react/components/MDXProvider.js +10 -0
- package/esm/src/react/components/ProviderComponent.d.ts +10 -0
- package/esm/src/react/components/ProviderComponent.d.ts.map +1 -0
- package/esm/src/react/components/ProviderComponent.js +38 -0
- package/esm/src/react/components/ai/agent-card.d.ts +21 -0
- package/esm/src/react/components/ai/agent-card.d.ts.map +1 -0
- package/esm/src/react/components/ai/agent-card.js +50 -0
- package/esm/src/react/components/ai/chat/components/animations.d.ts +9 -0
- package/esm/src/react/components/ai/chat/components/animations.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/components/animations.js +11 -0
- package/esm/src/react/components/ai/chat/components/empty-state.d.ts +33 -0
- package/esm/src/react/components/ai/chat/components/empty-state.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/components/empty-state.js +35 -0
- package/esm/src/react/components/ai/chat/components/message-actions.d.ts +7 -0
- package/esm/src/react/components/ai/chat/components/message-actions.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/components/message-actions.js +38 -0
- package/esm/src/react/components/ai/chat/components/reasoning.d.ts +8 -0
- package/esm/src/react/components/ai/chat/components/reasoning.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/components/reasoning.js +22 -0
- package/esm/src/react/components/ai/chat/components/tool-ui.d.ts +18 -0
- package/esm/src/react/components/ai/chat/components/tool-ui.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/components/tool-ui.js +109 -0
- package/esm/src/react/components/ai/chat/composition/api.d.ts +9 -0
- package/esm/src/react/components/ai/chat/composition/api.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/composition/api.js +23 -0
- package/esm/src/react/components/ai/chat/index.d.ts +44 -0
- package/esm/src/react/components/ai/chat/index.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/index.js +106 -0
- package/esm/src/react/components/ai/chat/utils/message-parts.d.ts +35 -0
- package/esm/src/react/components/ai/chat/utils/message-parts.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/utils/message-parts.js +56 -0
- package/esm/src/react/components/ai/chat.d.ts +2 -0
- package/esm/src/react/components/ai/chat.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat.js +1 -0
- package/esm/src/react/components/ai/error-boundary.d.ts +26 -0
- package/esm/src/react/components/ai/error-boundary.d.ts.map +1 -0
- package/esm/src/react/components/ai/error-boundary.js +46 -0
- package/esm/src/react/components/ai/icons/index.d.ts +19 -0
- package/esm/src/react/components/ai/icons/index.d.ts.map +1 -0
- package/esm/src/react/components/ai/icons/index.js +63 -0
- package/esm/src/react/components/ai/index.d.ts +31 -0
- package/esm/src/react/components/ai/index.d.ts.map +1 -0
- package/esm/src/react/components/ai/index.js +25 -0
- package/esm/src/react/components/ai/markdown.d.ts +19 -0
- package/esm/src/react/components/ai/markdown.d.ts.map +1 -0
- package/esm/src/react/components/ai/markdown.js +153 -0
- package/esm/src/react/components/ai/message.d.ts +38 -0
- package/esm/src/react/components/ai/message.d.ts.map +1 -0
- package/esm/src/react/components/ai/message.js +68 -0
- package/esm/src/react/components/ai/theme.d.ts +52 -0
- package/esm/src/react/components/ai/theme.d.ts.map +1 -0
- package/esm/src/react/components/ai/theme.js +65 -0
- package/esm/src/react/components/index.d.ts +14 -0
- package/esm/src/react/components/index.d.ts.map +1 -0
- package/esm/src/react/components/index.js +8 -0
- package/esm/src/react/components/optimized-image/OptimizedBackgroundImage.d.ts +13 -0
- package/esm/src/react/components/optimized-image/OptimizedBackgroundImage.d.ts.map +1 -0
- package/esm/src/react/components/optimized-image/OptimizedBackgroundImage.js +13 -0
- package/esm/src/react/components/optimized-image/OptimizedImage.d.ts +21 -0
- package/esm/src/react/components/optimized-image/OptimizedImage.d.ts.map +1 -0
- package/esm/src/react/components/optimized-image/OptimizedImage.js +18 -0
- package/esm/src/react/components/optimized-image/SimpleOptimizedImage.d.ts +6 -0
- package/esm/src/react/components/optimized-image/SimpleOptimizedImage.d.ts.map +1 -0
- package/esm/src/react/components/optimized-image/SimpleOptimizedImage.js +7 -0
- package/esm/src/react/components/optimized-image/helpers.d.ts +4 -0
- package/esm/src/react/components/optimized-image/helpers.d.ts.map +1 -0
- package/esm/src/react/components/optimized-image/helpers.js +13 -0
- package/esm/src/react/components/optimized-image/index.d.ts +7 -0
- package/esm/src/react/components/optimized-image/index.d.ts.map +1 -0
- package/esm/src/react/components/optimized-image/index.js +5 -0
- package/esm/src/react/components/optimized-image/useOptimizedImage.d.ts +12 -0
- package/esm/src/react/components/optimized-image/useOptimizedImage.d.ts.map +1 -0
- package/esm/src/react/components/optimized-image/useOptimizedImage.js +15 -0
- package/esm/src/react/components/optimized-image/utils.d.ts +11 -0
- package/esm/src/react/components/optimized-image/utils.d.ts.map +1 -0
- package/esm/src/react/components/optimized-image/utils.js +25 -0
- package/esm/src/react/context/index.d.ts +27 -0
- package/esm/src/react/context/index.d.ts.map +1 -0
- package/esm/src/react/context/index.js +23 -0
- package/esm/src/react/fonts/index.d.ts +15 -0
- package/esm/src/react/fonts/index.d.ts.map +1 -0
- package/esm/src/react/fonts/index.js +67 -0
- package/esm/src/react/head-collector.d.ts +48 -0
- package/esm/src/react/head-collector.d.ts.map +1 -0
- package/esm/src/react/head-collector.js +63 -0
- package/esm/src/react/index.d.ts +11 -0
- package/esm/src/react/index.d.ts.map +1 -0
- package/esm/src/react/index.js +8 -0
- package/esm/src/react/primitives/agent-primitives.d.ts +16 -0
- package/esm/src/react/primitives/agent-primitives.d.ts.map +1 -0
- package/esm/src/react/primitives/agent-primitives.js +28 -0
- package/esm/src/react/primitives/chat-container.d.ts +6 -0
- package/esm/src/react/primitives/chat-container.d.ts.map +1 -0
- package/esm/src/react/primitives/chat-container.js +5 -0
- package/esm/src/react/primitives/index.d.ts +63 -0
- package/esm/src/react/primitives/index.d.ts.map +1 -0
- package/esm/src/react/primitives/index.js +62 -0
- package/esm/src/react/primitives/input-box.d.ts +25 -0
- package/esm/src/react/primitives/input-box.d.ts.map +1 -0
- package/esm/src/react/primitives/input-box.js +65 -0
- package/esm/src/react/primitives/message-list.d.ts +22 -0
- package/esm/src/react/primitives/message-list.d.ts.map +1 -0
- package/esm/src/react/primitives/message-list.js +17 -0
- package/esm/src/react/primitives/tool-primitives.d.ts +36 -0
- package/esm/src/react/primitives/tool-primitives.d.ts.map +1 -0
- package/esm/src/react/primitives/tool-primitives.js +36 -0
- package/esm/src/react/router/index.d.ts +24 -0
- package/esm/src/react/router/index.d.ts.map +1 -0
- package/esm/src/react/router/index.js +24 -0
- package/esm/src/rendering/app-reserved.d.ts +26 -0
- package/esm/src/rendering/app-reserved.d.ts.map +1 -0
- package/esm/src/rendering/app-reserved.js +68 -0
- package/esm/src/rendering/app-route-resolver.d.ts +12 -0
- package/esm/src/rendering/app-route-resolver.d.ts.map +1 -0
- package/esm/src/rendering/app-route-resolver.js +143 -0
- package/esm/src/rendering/cache/cache-coordinator.d.ts +26 -0
- package/esm/src/rendering/cache/cache-coordinator.d.ts.map +1 -0
- package/esm/src/rendering/cache/cache-coordinator.js +67 -0
- package/esm/src/rendering/cache/index.d.ts +4 -0
- package/esm/src/rendering/cache/index.d.ts.map +1 -0
- package/esm/src/rendering/cache/index.js +2 -0
- package/esm/src/rendering/cache/stores/api-store.d.ts +30 -0
- package/esm/src/rendering/cache/stores/api-store.d.ts.map +1 -0
- package/esm/src/rendering/cache/stores/api-store.js +161 -0
- package/esm/src/rendering/cache/stores/filesystem-store.d.ts +20 -0
- package/esm/src/rendering/cache/stores/filesystem-store.d.ts.map +1 -0
- package/esm/src/rendering/cache/stores/filesystem-store.js +97 -0
- package/esm/src/rendering/cache/stores/index.d.ts +6 -0
- package/esm/src/rendering/cache/stores/index.d.ts.map +1 -0
- package/esm/src/rendering/cache/stores/index.js +5 -0
- package/esm/src/rendering/cache/stores/kv-store.d.ts +17 -0
- package/esm/src/rendering/cache/stores/kv-store.d.ts.map +1 -0
- package/esm/src/rendering/cache/stores/kv-store.js +74 -0
- package/esm/src/rendering/cache/stores/memory-store.d.ts +20 -0
- package/esm/src/rendering/cache/stores/memory-store.d.ts.map +1 -0
- package/esm/src/rendering/cache/stores/memory-store.js +56 -0
- package/esm/src/rendering/cache/stores/redis-store.d.ts +29 -0
- package/esm/src/rendering/cache/stores/redis-store.d.ts.map +1 -0
- package/esm/src/rendering/cache/stores/redis-store.js +215 -0
- package/esm/src/rendering/cache/types.d.ts +16 -0
- package/esm/src/rendering/cache/types.d.ts.map +1 -0
- package/esm/src/rendering/cache/types.js +1 -0
- package/esm/src/rendering/chunk-optimizer.d.ts +44 -0
- package/esm/src/rendering/chunk-optimizer.d.ts.map +1 -0
- package/esm/src/rendering/chunk-optimizer.js +179 -0
- package/esm/src/rendering/client/browser-logger.d.ts +27 -0
- package/esm/src/rendering/client/browser-logger.d.ts.map +1 -0
- package/esm/src/rendering/client/browser-logger.js +51 -0
- package/esm/src/rendering/client/index.d.ts +4 -0
- package/esm/src/rendering/client/index.d.ts.map +1 -0
- package/esm/src/rendering/client/index.js +3 -0
- package/esm/src/rendering/client/prefetch/link-observer.d.ts +26 -0
- package/esm/src/rendering/client/prefetch/link-observer.d.ts.map +1 -0
- package/esm/src/rendering/client/prefetch/link-observer.js +135 -0
- package/esm/src/rendering/client/prefetch/network-utils.d.ts +16 -0
- package/esm/src/rendering/client/prefetch/network-utils.d.ts.map +1 -0
- package/esm/src/rendering/client/prefetch/network-utils.js +32 -0
- package/esm/src/rendering/client/prefetch/prefetch-queue.d.ts +32 -0
- package/esm/src/rendering/client/prefetch/prefetch-queue.d.ts.map +1 -0
- package/esm/src/rendering/client/prefetch/prefetch-queue.js +138 -0
- package/esm/src/rendering/client/prefetch/resource-hints.d.ts +19 -0
- package/esm/src/rendering/client/prefetch/resource-hints.d.ts.map +1 -0
- package/esm/src/rendering/client/prefetch/resource-hints.js +108 -0
- package/esm/src/rendering/client/prefetch.d.ts +34 -0
- package/esm/src/rendering/client/prefetch.d.ts.map +1 -0
- package/esm/src/rendering/client/prefetch.js +108 -0
- package/esm/src/rendering/client/router.d.ts +53 -0
- package/esm/src/rendering/client/router.d.ts.map +1 -0
- package/esm/src/rendering/client/router.js +185 -0
- package/esm/src/rendering/client/state-bridge.d.ts +42 -0
- package/esm/src/rendering/client/state-bridge.d.ts.map +1 -0
- package/esm/src/rendering/client/state-bridge.js +146 -0
- package/esm/src/rendering/component-handling.d.ts +22 -0
- package/esm/src/rendering/component-handling.d.ts.map +1 -0
- package/esm/src/rendering/component-handling.js +99 -0
- package/esm/src/rendering/context/render-context.d.ts +35 -0
- package/esm/src/rendering/context/render-context.d.ts.map +1 -0
- package/esm/src/rendering/context/render-context.js +82 -0
- package/esm/src/rendering/element-validator/element-inspector.d.ts +7 -0
- package/esm/src/rendering/element-validator/element-inspector.d.ts.map +1 -0
- package/esm/src/rendering/element-validator/element-inspector.js +121 -0
- package/esm/src/rendering/element-validator/element-normalizer.d.ts +10 -0
- package/esm/src/rendering/element-validator/element-normalizer.d.ts.map +1 -0
- package/esm/src/rendering/element-validator/element-normalizer.js +48 -0
- package/esm/src/rendering/element-validator/index.d.ts +6 -0
- package/esm/src/rendering/element-validator/index.d.ts.map +1 -0
- package/esm/src/rendering/element-validator/index.js +4 -0
- package/esm/src/rendering/element-validator/primitive-checks.d.ts +15 -0
- package/esm/src/rendering/element-validator/primitive-checks.d.ts.map +1 -0
- package/esm/src/rendering/element-validator/primitive-checks.js +77 -0
- package/esm/src/rendering/element-validator/types.d.ts +15 -0
- package/esm/src/rendering/element-validator/types.d.ts.map +1 -0
- package/esm/src/rendering/element-validator/types.js +1 -0
- package/esm/src/rendering/element-validator/validator-core.d.ts +16 -0
- package/esm/src/rendering/element-validator/validator-core.d.ts.map +1 -0
- package/esm/src/rendering/element-validator/validator-core.js +35 -0
- package/esm/src/rendering/factories/service-factories.d.ts +31 -0
- package/esm/src/rendering/factories/service-factories.d.ts.map +1 -0
- package/esm/src/rendering/factories/service-factories.js +64 -0
- package/esm/src/rendering/index.d.ts +8 -0
- package/esm/src/rendering/index.d.ts.map +1 -0
- package/esm/src/rendering/index.js +10 -0
- package/esm/src/rendering/layouts/index.d.ts +10 -0
- package/esm/src/rendering/layouts/index.d.ts.map +1 -0
- package/esm/src/rendering/layouts/index.js +8 -0
- package/esm/src/rendering/layouts/layout-applicator.d.ts +49 -0
- package/esm/src/rendering/layouts/layout-applicator.d.ts.map +1 -0
- package/esm/src/rendering/layouts/layout-applicator.js +218 -0
- package/esm/src/rendering/layouts/layout-collector.d.ts +27 -0
- package/esm/src/rendering/layouts/layout-collector.d.ts.map +1 -0
- package/esm/src/rendering/layouts/layout-collector.js +236 -0
- package/esm/src/rendering/layouts/layout-compiler.d.ts +14 -0
- package/esm/src/rendering/layouts/layout-compiler.d.ts.map +1 -0
- package/esm/src/rendering/layouts/layout-compiler.js +44 -0
- package/esm/src/rendering/layouts/types.d.ts +13 -0
- package/esm/src/rendering/layouts/types.d.ts.map +1 -0
- package/esm/src/rendering/layouts/types.js +1 -0
- package/esm/src/rendering/layouts/utils/app-resolver.d.ts +4 -0
- package/esm/src/rendering/layouts/utils/app-resolver.d.ts.map +1 -0
- package/esm/src/rendering/layouts/utils/app-resolver.js +46 -0
- package/esm/src/rendering/layouts/utils/applicator.d.ts +8 -0
- package/esm/src/rendering/layouts/utils/applicator.d.ts.map +1 -0
- package/esm/src/rendering/layouts/utils/applicator.js +123 -0
- package/esm/src/rendering/layouts/utils/compiler.d.ts +4 -0
- package/esm/src/rendering/layouts/utils/compiler.d.ts.map +1 -0
- package/esm/src/rendering/layouts/utils/compiler.js +15 -0
- package/esm/src/rendering/layouts/utils/component-loader.d.ts +21 -0
- package/esm/src/rendering/layouts/utils/component-loader.d.ts.map +1 -0
- package/esm/src/rendering/layouts/utils/component-loader.js +143 -0
- package/esm/src/rendering/layouts/utils/discovery.d.ts +9 -0
- package/esm/src/rendering/layouts/utils/discovery.d.ts.map +1 -0
- package/esm/src/rendering/layouts/utils/discovery.js +128 -0
- package/esm/src/rendering/layouts/utils/ensure-valid-child.d.ts +9 -0
- package/esm/src/rendering/layouts/utils/ensure-valid-child.d.ts.map +1 -0
- package/esm/src/rendering/layouts/utils/ensure-valid-child.js +36 -0
- package/esm/src/rendering/layouts/utils/hash-calculator.d.ts +4 -0
- package/esm/src/rendering/layouts/utils/hash-calculator.d.ts.map +1 -0
- package/esm/src/rendering/layouts/utils/hash-calculator.js +33 -0
- package/esm/src/rendering/orchestrator/compiler-service.d.ts +9 -0
- package/esm/src/rendering/orchestrator/compiler-service.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/compiler-service.js +20 -0
- package/esm/src/rendering/orchestrator/config.d.ts +29 -0
- package/esm/src/rendering/orchestrator/config.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/config.js +84 -0
- package/esm/src/rendering/orchestrator/file-resolver/candidates.d.ts +12 -0
- package/esm/src/rendering/orchestrator/file-resolver/candidates.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/file-resolver/candidates.js +26 -0
- package/esm/src/rendering/orchestrator/file-resolver/index.d.ts +13 -0
- package/esm/src/rendering/orchestrator/file-resolver/index.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/file-resolver/index.js +38 -0
- package/esm/src/rendering/orchestrator/html.d.ts +39 -0
- package/esm/src/rendering/orchestrator/html.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/html.js +236 -0
- package/esm/src/rendering/orchestrator/layout.d.ts +42 -0
- package/esm/src/rendering/orchestrator/layout.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/layout.js +143 -0
- package/esm/src/rendering/orchestrator/lifecycle.d.ts +52 -0
- package/esm/src/rendering/orchestrator/lifecycle.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/lifecycle.js +193 -0
- package/esm/src/rendering/orchestrator/mdx.d.ts +25 -0
- package/esm/src/rendering/orchestrator/mdx.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/mdx.js +32 -0
- package/esm/src/rendering/orchestrator/module-loader/cache.d.ts +4 -0
- package/esm/src/rendering/orchestrator/module-loader/cache.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/module-loader/cache.js +19 -0
- package/esm/src/rendering/orchestrator/module-loader/esm-rewriter.d.ts +4 -0
- package/esm/src/rendering/orchestrator/module-loader/esm-rewriter.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/module-loader/esm-rewriter.js +64 -0
- package/esm/src/rendering/orchestrator/module-loader/index.d.ts +38 -0
- package/esm/src/rendering/orchestrator/module-loader/index.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/module-loader/index.js +229 -0
- package/esm/src/rendering/orchestrator/pipeline.d.ts +66 -0
- package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/pipeline.js +474 -0
- package/esm/src/rendering/orchestrator/ssr-orchestrator.d.ts +24 -0
- package/esm/src/rendering/orchestrator/ssr-orchestrator.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/ssr-orchestrator.js +86 -0
- package/esm/src/rendering/orchestrator/ssr.d.ts +38 -0
- package/esm/src/rendering/orchestrator/ssr.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/ssr.js +180 -0
- package/esm/src/rendering/orchestrator/types.d.ts +99 -0
- package/esm/src/rendering/orchestrator/types.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/types.js +1 -0
- package/esm/src/rendering/page-renderer.d.ts +55 -0
- package/esm/src/rendering/page-renderer.d.ts.map +1 -0
- package/esm/src/rendering/page-renderer.js +147 -0
- package/esm/src/rendering/page-rendering.d.ts +21 -0
- package/esm/src/rendering/page-rendering.d.ts.map +1 -0
- package/esm/src/rendering/page-rendering.js +68 -0
- package/esm/src/rendering/page-resolution/index.d.ts +3 -0
- package/esm/src/rendering/page-resolution/index.d.ts.map +1 -0
- package/esm/src/rendering/page-resolution/index.js +2 -0
- package/esm/src/rendering/page-resolution/page-resolver.d.ts +19 -0
- package/esm/src/rendering/page-resolution/page-resolver.d.ts.map +1 -0
- package/esm/src/rendering/page-resolution/page-resolver.js +85 -0
- package/esm/src/rendering/renderer.d.ts +65 -0
- package/esm/src/rendering/renderer.d.ts.map +1 -0
- package/esm/src/rendering/renderer.js +379 -0
- package/esm/src/rendering/route-params-extractor.d.ts +4 -0
- package/esm/src/rendering/route-params-extractor.d.ts.map +1 -0
- package/esm/src/rendering/route-params-extractor.js +132 -0
- package/esm/src/rendering/router-detection.d.ts +27 -0
- package/esm/src/rendering/router-detection.d.ts.map +1 -0
- package/esm/src/rendering/router-detection.js +135 -0
- package/esm/src/rendering/rsc/component-analyzer.d.ts +5 -0
- package/esm/src/rendering/rsc/component-analyzer.d.ts.map +1 -0
- package/esm/src/rendering/rsc/component-analyzer.js +122 -0
- package/esm/src/rendering/rsc/export-extractor.d.ts +6 -0
- package/esm/src/rendering/rsc/export-extractor.d.ts.map +1 -0
- package/esm/src/rendering/rsc/export-extractor.js +39 -0
- package/esm/src/rendering/rsc/production-optimizer.d.ts +26 -0
- package/esm/src/rendering/rsc/production-optimizer.d.ts.map +1 -0
- package/esm/src/rendering/rsc/production-optimizer.js +103 -0
- package/esm/src/rendering/rsc/server-action-guard.d.ts +6 -0
- package/esm/src/rendering/rsc/server-action-guard.d.ts.map +1 -0
- package/esm/src/rendering/rsc/server-action-guard.js +3 -0
- package/esm/src/rendering/rsc/server-renderer/component-detector.d.ts +14 -0
- package/esm/src/rendering/rsc/server-renderer/component-detector.d.ts.map +1 -0
- package/esm/src/rendering/rsc/server-renderer/component-detector.js +19 -0
- package/esm/src/rendering/rsc/server-renderer/html-generator.d.ts +6 -0
- package/esm/src/rendering/rsc/server-renderer/html-generator.d.ts.map +1 -0
- package/esm/src/rendering/rsc/server-renderer/html-generator.js +35 -0
- package/esm/src/rendering/rsc/server-renderer/index.d.ts +6 -0
- package/esm/src/rendering/rsc/server-renderer/index.d.ts.map +1 -0
- package/esm/src/rendering/rsc/server-renderer/index.js +5 -0
- package/esm/src/rendering/rsc/server-renderer/prop-serializer.d.ts +9 -0
- package/esm/src/rendering/rsc/server-renderer/prop-serializer.d.ts.map +1 -0
- package/esm/src/rendering/rsc/server-renderer/prop-serializer.js +62 -0
- package/esm/src/rendering/rsc/server-renderer/rsc-renderer.d.ts +10 -0
- package/esm/src/rendering/rsc/server-renderer/rsc-renderer.d.ts.map +1 -0
- package/esm/src/rendering/rsc/server-renderer/rsc-renderer.js +31 -0
- package/esm/src/rendering/rsc/server-renderer/tree-processor.d.ts +8 -0
- package/esm/src/rendering/rsc/server-renderer/tree-processor.d.ts.map +1 -0
- package/esm/src/rendering/rsc/server-renderer/tree-processor.js +76 -0
- package/esm/src/rendering/rsc/types.d.ts +2 -0
- package/esm/src/rendering/rsc/types.d.ts.map +1 -0
- package/esm/src/rendering/rsc/types.js +1 -0
- package/esm/src/rendering/script-page-handling.d.ts +15 -0
- package/esm/src/rendering/script-page-handling.d.ts.map +1 -0
- package/esm/src/rendering/script-page-handling.js +271 -0
- package/esm/src/rendering/shared/context-aware-cache.d.ts +33 -0
- package/esm/src/rendering/shared/context-aware-cache.d.ts.map +1 -0
- package/esm/src/rendering/shared/context-aware-cache.js +161 -0
- package/esm/src/rendering/shared/shared-services.d.ts +30 -0
- package/esm/src/rendering/shared/shared-services.d.ts.map +1 -0
- package/esm/src/rendering/shared/shared-services.js +85 -0
- package/esm/src/rendering/snippet-renderer.d.ts +29 -0
- package/esm/src/rendering/snippet-renderer.d.ts.map +1 -0
- package/esm/src/rendering/snippet-renderer.js +273 -0
- package/esm/src/rendering/ssr/component-registry.d.ts +130 -0
- package/esm/src/rendering/ssr/component-registry.d.ts.map +1 -0
- package/esm/src/rendering/ssr/component-registry.js +318 -0
- package/esm/src/rendering/ssr-globals/context.d.ts +12 -0
- package/esm/src/rendering/ssr-globals/context.d.ts.map +1 -0
- package/esm/src/rendering/ssr-globals/context.js +43 -0
- package/esm/src/rendering/ssr-globals/dom-stubs.d.ts +217 -0
- package/esm/src/rendering/ssr-globals/dom-stubs.d.ts.map +1 -0
- package/esm/src/rendering/ssr-globals/dom-stubs.js +217 -0
- package/esm/src/rendering/ssr-globals/fetch-interceptor.d.ts +3 -0
- package/esm/src/rendering/ssr-globals/fetch-interceptor.d.ts.map +1 -0
- package/esm/src/rendering/ssr-globals/fetch-interceptor.js +115 -0
- package/esm/src/rendering/ssr-globals/index.d.ts +5 -0
- package/esm/src/rendering/ssr-globals/index.d.ts.map +1 -0
- package/esm/src/rendering/ssr-globals/index.js +60 -0
- package/esm/src/rendering/ssr-globals.d.ts +2 -0
- package/esm/src/rendering/ssr-globals.d.ts.map +1 -0
- package/esm/src/rendering/ssr-globals.js +1 -0
- package/esm/src/rendering/ssr-renderer.d.ts +37 -0
- package/esm/src/rendering/ssr-renderer.d.ts.map +1 -0
- package/esm/src/rendering/ssr-renderer.js +169 -0
- package/esm/src/rendering/utils/index.d.ts +4 -0
- package/esm/src/rendering/utils/index.d.ts.map +1 -0
- package/esm/src/rendering/utils/index.js +3 -0
- package/esm/src/rendering/utils/react-helpers.d.ts +5 -0
- package/esm/src/rendering/utils/react-helpers.d.ts.map +1 -0
- package/esm/src/rendering/utils/react-helpers.js +22 -0
- package/esm/src/rendering/utils/stream-utils.d.ts +11 -0
- package/esm/src/rendering/utils/stream-utils.d.ts.map +1 -0
- package/esm/src/rendering/utils/stream-utils.js +104 -0
- package/esm/src/rendering/virtual-module-system.d.ts +21 -0
- package/esm/src/rendering/virtual-module-system.d.ts.map +1 -0
- package/esm/src/rendering/virtual-module-system.js +88 -0
- package/esm/src/resource/factory.d.ts +10 -0
- package/esm/src/resource/factory.d.ts.map +1 -0
- package/esm/src/resource/factory.js +49 -0
- package/esm/src/resource/index.d.ts +5 -0
- package/esm/src/resource/index.d.ts.map +1 -0
- package/esm/src/resource/index.js +3 -0
- package/esm/src/resource/registry.d.ts +17 -0
- package/esm/src/resource/registry.d.ts.map +1 -0
- package/esm/src/resource/registry.js +45 -0
- package/esm/src/resource/types.d.ts +58 -0
- package/esm/src/resource/types.d.ts.map +1 -0
- package/esm/src/resource/types.js +8 -0
- package/esm/src/routing/api/api-route-matcher.d.ts +27 -0
- package/esm/src/routing/api/api-route-matcher.d.ts.map +1 -0
- package/esm/src/routing/api/api-route-matcher.js +142 -0
- package/esm/src/routing/api/context-builder.d.ts +20 -0
- package/esm/src/routing/api/context-builder.d.ts.map +1 -0
- package/esm/src/routing/api/context-builder.js +34 -0
- package/esm/src/routing/api/error-handler.d.ts +4 -0
- package/esm/src/routing/api/error-handler.d.ts.map +1 -0
- package/esm/src/routing/api/error-handler.js +21 -0
- package/esm/src/routing/api/handler.d.ts +37 -0
- package/esm/src/routing/api/handler.d.ts.map +1 -0
- package/esm/src/routing/api/handler.js +221 -0
- package/esm/src/routing/api/index.d.ts +9 -0
- package/esm/src/routing/api/index.d.ts.map +1 -0
- package/esm/src/routing/api/index.js +5 -0
- package/esm/src/routing/api/method-validator.d.ts +4 -0
- package/esm/src/routing/api/method-validator.d.ts.map +1 -0
- package/esm/src/routing/api/method-validator.js +10 -0
- package/esm/src/routing/api/module-loader/esbuild-plugin.d.ts +10 -0
- package/esm/src/routing/api/module-loader/esbuild-plugin.d.ts.map +1 -0
- package/esm/src/routing/api/module-loader/esbuild-plugin.js +164 -0
- package/esm/src/routing/api/module-loader/http-validator.d.ts +2 -0
- package/esm/src/routing/api/module-loader/http-validator.d.ts.map +1 -0
- package/esm/src/routing/api/module-loader/http-validator.js +33 -0
- package/esm/src/routing/api/module-loader/loader.d.ts +3 -0
- package/esm/src/routing/api/module-loader/loader.d.ts.map +1 -0
- package/esm/src/routing/api/module-loader/loader.js +408 -0
- package/esm/src/routing/api/module-loader/security-config.d.ts +3 -0
- package/esm/src/routing/api/module-loader/security-config.d.ts.map +1 -0
- package/esm/src/routing/api/module-loader/security-config.js +14 -0
- package/esm/src/routing/api/module-loader/types.d.ts +21 -0
- package/esm/src/routing/api/module-loader/types.d.ts.map +1 -0
- package/esm/src/routing/api/module-loader/types.js +1 -0
- package/esm/src/routing/api/openapi/path-utils.d.ts +95 -0
- package/esm/src/routing/api/openapi/path-utils.d.ts.map +1 -0
- package/esm/src/routing/api/openapi/path-utils.js +137 -0
- package/esm/src/routing/api/openapi/spec-generator.d.ts +28 -0
- package/esm/src/routing/api/openapi/spec-generator.d.ts.map +1 -0
- package/esm/src/routing/api/openapi/spec-generator.js +205 -0
- package/esm/src/routing/api/openapi/types.d.ts +127 -0
- package/esm/src/routing/api/openapi/types.d.ts.map +1 -0
- package/esm/src/routing/api/openapi/types.js +12 -0
- package/esm/src/routing/api/route-discovery.d.ts +5 -0
- package/esm/src/routing/api/route-discovery.d.ts.map +1 -0
- package/esm/src/routing/api/route-discovery.js +45 -0
- package/esm/src/routing/api/route-executor.d.ts +7 -0
- package/esm/src/routing/api/route-executor.d.ts.map +1 -0
- package/esm/src/routing/api/route-executor.js +78 -0
- package/esm/src/routing/client/dom-utils.d.ts +13 -0
- package/esm/src/routing/client/dom-utils.d.ts.map +1 -0
- package/esm/src/routing/client/dom-utils.js +138 -0
- package/esm/src/routing/client/index.d.ts +8 -0
- package/esm/src/routing/client/index.d.ts.map +1 -0
- package/esm/src/routing/client/index.js +5 -0
- package/esm/src/routing/client/navigation-handlers.d.ts +26 -0
- package/esm/src/routing/client/navigation-handlers.d.ts.map +1 -0
- package/esm/src/routing/client/navigation-handlers.js +109 -0
- package/esm/src/routing/client/page-loader.d.ts +26 -0
- package/esm/src/routing/client/page-loader.d.ts.map +1 -0
- package/esm/src/routing/client/page-loader.js +164 -0
- package/esm/src/routing/client/page-transition.d.ts +13 -0
- package/esm/src/routing/client/page-transition.d.ts.map +1 -0
- package/esm/src/routing/client/page-transition.js +77 -0
- package/esm/src/routing/client/types.d.ts +35 -0
- package/esm/src/routing/client/types.d.ts.map +1 -0
- package/esm/src/routing/client/types.js +1 -0
- package/esm/src/routing/client/viewport-prefetch.d.ts +15 -0
- package/esm/src/routing/client/viewport-prefetch.d.ts.map +1 -0
- package/esm/src/routing/client/viewport-prefetch.js +73 -0
- package/esm/src/routing/index.d.ts +9 -0
- package/esm/src/routing/index.d.ts.map +1 -0
- package/esm/src/routing/index.js +4 -0
- package/esm/src/routing/matchers/index.d.ts +6 -0
- package/esm/src/routing/matchers/index.d.ts.map +1 -0
- package/esm/src/routing/matchers/index.js +4 -0
- package/esm/src/routing/matchers/pattern-route-matcher.d.ts +10 -0
- package/esm/src/routing/matchers/pattern-route-matcher.d.ts.map +1 -0
- package/esm/src/routing/matchers/pattern-route-matcher.js +33 -0
- package/esm/src/routing/matchers/route-matcher.d.ts +3 -0
- package/esm/src/routing/matchers/route-matcher.d.ts.map +1 -0
- package/esm/src/routing/matchers/route-matcher.js +34 -0
- package/esm/src/routing/matchers/route-parser.d.ts +4 -0
- package/esm/src/routing/matchers/route-parser.d.ts.map +1 -0
- package/esm/src/routing/matchers/route-parser.js +60 -0
- package/esm/src/routing/matchers/types.d.ts +13 -0
- package/esm/src/routing/matchers/types.d.ts.map +1 -0
- package/esm/src/routing/matchers/types.js +1 -0
- package/esm/src/routing/registry/index.d.ts +2 -0
- package/esm/src/routing/registry/index.d.ts.map +1 -0
- package/esm/src/routing/registry/index.js +1 -0
- package/esm/src/routing/registry/registry.d.ts +20 -0
- package/esm/src/routing/registry/registry.d.ts.map +1 -0
- package/esm/src/routing/registry/registry.js +111 -0
- package/esm/src/routing/registry/types.d.ts +2 -0
- package/esm/src/routing/registry/types.d.ts.map +1 -0
- package/esm/src/routing/registry/types.js +1 -0
- package/esm/src/routing/slug-mapper/dynamic-route-matcher.d.ts +5 -0
- package/esm/src/routing/slug-mapper/dynamic-route-matcher.d.ts.map +1 -0
- package/esm/src/routing/slug-mapper/dynamic-route-matcher.js +49 -0
- package/esm/src/routing/slug-mapper/index.d.ts +5 -0
- package/esm/src/routing/slug-mapper/index.d.ts.map +1 -0
- package/esm/src/routing/slug-mapper/index.js +3 -0
- package/esm/src/routing/slug-mapper/path-candidate-generator.d.ts +6 -0
- package/esm/src/routing/slug-mapper/path-candidate-generator.d.ts.map +1 -0
- package/esm/src/routing/slug-mapper/path-candidate-generator.js +43 -0
- package/esm/src/routing/slug-mapper/slug-normalizer.d.ts +5 -0
- package/esm/src/routing/slug-mapper/slug-normalizer.d.ts.map +1 -0
- package/esm/src/routing/slug-mapper/slug-normalizer.js +22 -0
- package/esm/src/routing/slug-mapper/types.d.ts +8 -0
- package/esm/src/routing/slug-mapper/types.d.ts.map +1 -0
- package/esm/src/routing/slug-mapper/types.js +1 -0
- package/esm/src/security/client/html-sanitizer.d.ts +39 -0
- package/esm/src/security/client/html-sanitizer.d.ts.map +1 -0
- package/esm/src/security/client/html-sanitizer.js +94 -0
- package/esm/src/security/http/auth.d.ts +17 -0
- package/esm/src/security/http/auth.d.ts.map +1 -0
- package/esm/src/security/http/auth.js +99 -0
- package/esm/src/security/http/base-handler.d.ts +28 -0
- package/esm/src/security/http/base-handler.d.ts.map +1 -0
- package/esm/src/security/http/base-handler.js +106 -0
- package/esm/src/security/http/client-hints.d.ts +24 -0
- package/esm/src/security/http/client-hints.d.ts.map +1 -0
- package/esm/src/security/http/client-hints.js +26 -0
- package/esm/src/security/http/config.d.ts +24 -0
- package/esm/src/security/http/config.d.ts.map +1 -0
- package/esm/src/security/http/config.js +83 -0
- package/esm/src/security/http/cors/constants.d.ts +14 -0
- package/esm/src/security/http/cors/constants.d.ts.map +1 -0
- package/esm/src/security/http/cors/constants.js +13 -0
- package/esm/src/security/http/cors/headers.d.ts +6 -0
- package/esm/src/security/http/cors/headers.d.ts.map +1 -0
- package/esm/src/security/http/cors/headers.js +60 -0
- package/esm/src/security/http/cors/index.d.ts +8 -0
- package/esm/src/security/http/cors/index.d.ts.map +1 -0
- package/esm/src/security/http/cors/index.js +6 -0
- package/esm/src/security/http/cors/middleware.d.ts +5 -0
- package/esm/src/security/http/cors/middleware.d.ts.map +1 -0
- package/esm/src/security/http/cors/middleware.js +30 -0
- package/esm/src/security/http/cors/preflight.d.ts +5 -0
- package/esm/src/security/http/cors/preflight.d.ts.map +1 -0
- package/esm/src/security/http/cors/preflight.js +57 -0
- package/esm/src/security/http/cors/types.d.ts +28 -0
- package/esm/src/security/http/cors/types.d.ts.map +1 -0
- package/esm/src/security/http/cors/types.js +1 -0
- package/esm/src/security/http/cors/validators.d.ts +11 -0
- package/esm/src/security/http/cors/validators.d.ts.map +1 -0
- package/esm/src/security/http/cors/validators.js +147 -0
- package/esm/src/security/http/handlers-index.d.ts +5 -0
- package/esm/src/security/http/handlers-index.d.ts.map +1 -0
- package/esm/src/security/http/handlers-index.js +3 -0
- package/esm/src/security/http/middleware/config-loader.d.ts +5 -0
- package/esm/src/security/http/middleware/config-loader.d.ts.map +1 -0
- package/esm/src/security/http/middleware/config-loader.js +39 -0
- package/esm/src/security/http/middleware/content-types.d.ts +2 -0
- package/esm/src/security/http/middleware/content-types.d.ts.map +1 -0
- package/esm/src/security/http/middleware/content-types.js +20 -0
- package/esm/src/security/http/middleware/cors-handler.d.ts +4 -0
- package/esm/src/security/http/middleware/cors-handler.d.ts.map +1 -0
- package/esm/src/security/http/middleware/cors-handler.js +11 -0
- package/esm/src/security/http/middleware/etag.d.ts +2 -0
- package/esm/src/security/http/middleware/etag.d.ts.map +1 -0
- package/esm/src/security/http/middleware/etag.js +8 -0
- package/esm/src/security/http/middleware/index.d.ts +6 -0
- package/esm/src/security/http/middleware/index.d.ts.map +1 -0
- package/esm/src/security/http/middleware/index.js +4 -0
- package/esm/src/security/http/middleware/types.d.ts +33 -0
- package/esm/src/security/http/middleware/types.d.ts.map +1 -0
- package/esm/src/security/http/middleware/types.js +1 -0
- package/esm/src/security/http/response/builder.d.ts +41 -0
- package/esm/src/security/http/response/builder.d.ts.map +1 -0
- package/esm/src/security/http/response/builder.js +50 -0
- package/esm/src/security/http/response/cache-handler.d.ts +3 -0
- package/esm/src/security/http/response/cache-handler.d.ts.map +1 -0
- package/esm/src/security/http/response/cache-handler.js +25 -0
- package/esm/src/security/http/response/constants.d.ts +14 -0
- package/esm/src/security/http/response/constants.d.ts.map +1 -0
- package/esm/src/security/http/response/constants.js +13 -0
- package/esm/src/security/http/response/fluent-methods.d.ts +34 -0
- package/esm/src/security/http/response/fluent-methods.d.ts.map +1 -0
- package/esm/src/security/http/response/fluent-methods.js +77 -0
- package/esm/src/security/http/response/index.d.ts +7 -0
- package/esm/src/security/http/response/index.d.ts.map +1 -0
- package/esm/src/security/http/response/index.js +5 -0
- package/esm/src/security/http/response/response-methods.d.ts +13 -0
- package/esm/src/security/http/response/response-methods.d.ts.map +1 -0
- package/esm/src/security/http/response/response-methods.js +38 -0
- package/esm/src/security/http/response/security-handler.d.ts +15 -0
- package/esm/src/security/http/response/security-handler.d.ts.map +1 -0
- package/esm/src/security/http/response/security-handler.js +98 -0
- package/esm/src/security/http/response/static-helpers.d.ts +57 -0
- package/esm/src/security/http/response/static-helpers.d.ts.map +1 -0
- package/esm/src/security/http/response/static-helpers.js +57 -0
- package/esm/src/security/http/response/types.d.ts +48 -0
- package/esm/src/security/http/response/types.d.ts.map +1 -0
- package/esm/src/security/http/response/types.js +5 -0
- package/esm/src/security/index.d.ts +16 -0
- package/esm/src/security/index.d.ts.map +1 -0
- package/esm/src/security/index.js +8 -0
- package/esm/src/security/input-validation/errors.d.ts +5 -0
- package/esm/src/security/input-validation/errors.d.ts.map +1 -0
- package/esm/src/security/input-validation/errors.js +8 -0
- package/esm/src/security/input-validation/handler.d.ts +12 -0
- package/esm/src/security/input-validation/handler.d.ts.map +1 -0
- package/esm/src/security/input-validation/handler.js +29 -0
- package/esm/src/security/input-validation/index.d.ts +9 -0
- package/esm/src/security/input-validation/index.d.ts.map +1 -0
- package/esm/src/security/input-validation/index.js +7 -0
- package/esm/src/security/input-validation/limits.d.ts +5 -0
- package/esm/src/security/input-validation/limits.d.ts.map +1 -0
- package/esm/src/security/input-validation/limits.js +75 -0
- package/esm/src/security/input-validation/parsers.d.ts +7 -0
- package/esm/src/security/input-validation/parsers.d.ts.map +1 -0
- package/esm/src/security/input-validation/parsers.js +89 -0
- package/esm/src/security/input-validation/sanitizers.d.ts +3 -0
- package/esm/src/security/input-validation/sanitizers.d.ts.map +1 -0
- package/esm/src/security/input-validation/sanitizers.js +38 -0
- package/esm/src/security/input-validation/schemas.d.ts +42 -0
- package/esm/src/security/input-validation/schemas.d.ts.map +1 -0
- package/esm/src/security/input-validation/schemas.js +30 -0
- package/esm/src/security/input-validation/types.d.ts +19 -0
- package/esm/src/security/input-validation/types.d.ts.map +1 -0
- package/esm/src/security/input-validation/types.js +7 -0
- package/esm/src/security/path-validation/canonical.d.ts +22 -0
- package/esm/src/security/path-validation/canonical.d.ts.map +1 -0
- package/esm/src/security/path-validation/canonical.js +56 -0
- package/esm/src/security/path-validation/index.d.ts +26 -0
- package/esm/src/security/path-validation/index.d.ts.map +1 -0
- package/esm/src/security/path-validation/index.js +94 -0
- package/esm/src/security/path-validation/normalization.d.ts +29 -0
- package/esm/src/security/path-validation/normalization.d.ts.map +1 -0
- package/esm/src/security/path-validation/normalization.js +60 -0
- package/esm/src/security/path-validation/presets.d.ts +8 -0
- package/esm/src/security/path-validation/presets.d.ts.map +1 -0
- package/esm/src/security/path-validation/presets.js +56 -0
- package/esm/src/security/path-validation/rules.d.ts +10 -0
- package/esm/src/security/path-validation/rules.d.ts.map +1 -0
- package/esm/src/security/path-validation/rules.js +56 -0
- package/esm/src/security/path-validation/types.d.ts +34 -0
- package/esm/src/security/path-validation/types.d.ts.map +1 -0
- package/esm/src/security/path-validation/types.js +16 -0
- package/esm/src/security/path-validation.d.ts +2 -0
- package/esm/src/security/path-validation.d.ts.map +1 -0
- package/esm/src/security/path-validation.js +1 -0
- package/esm/src/security/sandbox/constants.d.ts +7 -0
- package/esm/src/security/sandbox/constants.d.ts.map +1 -0
- package/esm/src/security/sandbox/constants.js +6 -0
- package/esm/src/security/sandbox/deno-sandbox.d.ts +6 -0
- package/esm/src/security/sandbox/deno-sandbox.d.ts.map +1 -0
- package/esm/src/security/sandbox/deno-sandbox.js +77 -0
- package/esm/src/security/sandbox/permission-system.d.ts +11 -0
- package/esm/src/security/sandbox/permission-system.d.ts.map +1 -0
- package/esm/src/security/sandbox/permission-system.js +63 -0
- package/esm/src/security/secure-fs.d.ts +63 -0
- package/esm/src/security/secure-fs.d.ts.map +1 -0
- package/esm/src/security/secure-fs.js +196 -0
- package/esm/src/server/bootstrap.d.ts +16 -0
- package/esm/src/server/bootstrap.d.ts.map +1 -0
- package/esm/src/server/bootstrap.js +110 -0
- package/esm/src/server/build-app-route-renderer.d.ts +15 -0
- package/esm/src/server/build-app-route-renderer.d.ts.map +1 -0
- package/esm/src/server/build-app-route-renderer.js +221 -0
- package/esm/src/server/build-routes.d.ts +11 -0
- package/esm/src/server/build-routes.d.ts.map +1 -0
- package/esm/src/server/build-routes.js +93 -0
- package/esm/src/server/build-service-worker.d.ts +9 -0
- package/esm/src/server/build-service-worker.d.ts.map +1 -0
- package/esm/src/server/build-service-worker.js +173 -0
- package/esm/src/server/build-types.d.ts +39 -0
- package/esm/src/server/build-types.d.ts.map +1 -0
- package/esm/src/server/build-types.js +5 -0
- package/esm/src/server/context/cache-invalidation.d.ts +14 -0
- package/esm/src/server/context/cache-invalidation.d.ts.map +1 -0
- package/esm/src/server/context/cache-invalidation.js +98 -0
- package/esm/src/server/context/enriched-context.d.ts +67 -0
- package/esm/src/server/context/enriched-context.d.ts.map +1 -0
- package/esm/src/server/context/enriched-context.js +56 -0
- package/esm/src/server/context/request-context.d.ts +17 -0
- package/esm/src/server/context/request-context.d.ts.map +1 -0
- package/esm/src/server/context/request-context.js +44 -0
- package/esm/src/server/dev-server/error-overlay/error-formatter.d.ts +12 -0
- package/esm/src/server/dev-server/error-overlay/error-formatter.d.ts.map +1 -0
- package/esm/src/server/dev-server/error-overlay/error-formatter.js +37 -0
- package/esm/src/server/dev-server/error-overlay/html-template.d.ts +4 -0
- package/esm/src/server/dev-server/error-overlay/html-template.d.ts.map +1 -0
- package/esm/src/server/dev-server/error-overlay/html-template.js +300 -0
- package/esm/src/server/dev-server/error-overlay/index.d.ts +9 -0
- package/esm/src/server/dev-server/error-overlay/index.d.ts.map +1 -0
- package/esm/src/server/dev-server/error-overlay/index.js +8 -0
- package/esm/src/server/dev-server/error-overlay/overlay-renderer.d.ts +8 -0
- package/esm/src/server/dev-server/error-overlay/overlay-renderer.d.ts.map +1 -0
- package/esm/src/server/dev-server/error-overlay/overlay-renderer.js +10 -0
- package/esm/src/server/dev-server/error-overlay/stack-parser.d.ts +11 -0
- package/esm/src/server/dev-server/error-overlay/stack-parser.d.ts.map +1 -0
- package/esm/src/server/dev-server/error-overlay/stack-parser.js +15 -0
- package/esm/src/server/dev-server/file-watch-setup.d.ts +24 -0
- package/esm/src/server/dev-server/file-watch-setup.d.ts.map +1 -0
- package/esm/src/server/dev-server/file-watch-setup.js +131 -0
- package/esm/src/server/dev-server/file-watcher.d.ts +15 -0
- package/esm/src/server/dev-server/file-watcher.d.ts.map +1 -0
- package/esm/src/server/dev-server/file-watcher.js +78 -0
- package/esm/src/server/dev-server/hmr/index.d.ts +8 -0
- package/esm/src/server/dev-server/hmr/index.d.ts.map +1 -0
- package/esm/src/server/dev-server/hmr/index.js +5 -0
- package/esm/src/server/dev-server/hmr/message-handler.d.ts +7 -0
- package/esm/src/server/dev-server/hmr/message-handler.d.ts.map +1 -0
- package/esm/src/server/dev-server/hmr/message-handler.js +6 -0
- package/esm/src/server/dev-server/hmr/runtime-generator.d.ts +6 -0
- package/esm/src/server/dev-server/hmr/runtime-generator.d.ts.map +1 -0
- package/esm/src/server/dev-server/hmr/runtime-generator.js +9 -0
- package/esm/src/server/dev-server/hmr/templates.d.ts +2 -0
- package/esm/src/server/dev-server/hmr/templates.d.ts.map +1 -0
- package/esm/src/server/dev-server/hmr/templates.js +151 -0
- package/esm/src/server/dev-server/hmr-server.d.ts +47 -0
- package/esm/src/server/dev-server/hmr-server.d.ts.map +1 -0
- package/esm/src/server/dev-server/hmr-server.js +166 -0
- package/esm/src/server/dev-server/hmr-types.d.ts +26 -0
- package/esm/src/server/dev-server/hmr-types.d.ts.map +1 -0
- package/esm/src/server/dev-server/hmr-types.js +1 -0
- package/esm/src/server/dev-server/index.d.ts +5 -0
- package/esm/src/server/dev-server/index.d.ts.map +1 -0
- package/esm/src/server/dev-server/index.js +8 -0
- package/esm/src/server/dev-server/middleware.d.ts +12 -0
- package/esm/src/server/dev-server/middleware.d.ts.map +1 -0
- package/esm/src/server/dev-server/middleware.js +182 -0
- package/esm/src/server/dev-server/request-handler.d.ts +24 -0
- package/esm/src/server/dev-server/request-handler.d.ts.map +1 -0
- package/esm/src/server/dev-server/request-handler.js +148 -0
- package/esm/src/server/dev-server/route-discovery.d.ts +21 -0
- package/esm/src/server/dev-server/route-discovery.d.ts.map +1 -0
- package/esm/src/server/dev-server/route-discovery.js +214 -0
- package/esm/src/server/dev-server/server.d.ts +26 -0
- package/esm/src/server/dev-server/server.d.ts.map +1 -0
- package/esm/src/server/dev-server/server.js +209 -0
- package/esm/src/server/dev-server/types.d.ts +29 -0
- package/esm/src/server/dev-server/types.d.ts.map +1 -0
- package/esm/src/server/dev-server/types.js +1 -0
- package/esm/src/server/dev-server.d.ts +2 -0
- package/esm/src/server/dev-server.d.ts.map +1 -0
- package/esm/src/server/dev-server.js +1 -0
- package/esm/src/server/handlers/dev/dashboard/api.d.ts +4 -0
- package/esm/src/server/handlers/dev/dashboard/api.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/dashboard/api.js +511 -0
- package/esm/src/server/handlers/dev/dashboard/html-shell.d.ts +2 -0
- package/esm/src/server/handlers/dev/dashboard/html-shell.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/dashboard/html-shell.js +43 -0
- package/esm/src/server/handlers/dev/dashboard/index.d.ts +10 -0
- package/esm/src/server/handlers/dev/dashboard/index.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/dashboard/index.js +41 -0
- package/esm/src/server/handlers/dev/dashboard/ui-handler.d.ts +3 -0
- package/esm/src/server/handlers/dev/dashboard/ui-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/dashboard/ui-handler.js +89 -0
- package/esm/src/server/handlers/dev/debug-context.d.ts +18 -0
- package/esm/src/server/handlers/dev/debug-context.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/debug-context.js +82 -0
- package/esm/src/server/handlers/dev/endpoints.d.ts +19 -0
- package/esm/src/server/handlers/dev/endpoints.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/endpoints.js +608 -0
- package/esm/src/server/handlers/dev/files/dev-file-handler.d.ts +9 -0
- package/esm/src/server/handlers/dev/files/dev-file-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/files/dev-file-handler.js +46 -0
- package/esm/src/server/handlers/dev/files/esbuild-bundler.d.ts +3 -0
- package/esm/src/server/handlers/dev/files/esbuild-bundler.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/files/esbuild-bundler.js +30 -0
- package/esm/src/server/handlers/dev/files/esbuild-plugins.d.ts +14 -0
- package/esm/src/server/handlers/dev/files/esbuild-plugins.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/files/esbuild-plugins.js +165 -0
- package/esm/src/server/handlers/dev/files/index.d.ts +12 -0
- package/esm/src/server/handlers/dev/files/index.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/files/index.js +11 -0
- package/esm/src/server/handlers/dev/files/path-validator.d.ts +3 -0
- package/esm/src/server/handlers/dev/files/path-validator.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/files/path-validator.js +42 -0
- package/esm/src/server/handlers/dev/projects/api.d.ts +4 -0
- package/esm/src/server/handlers/dev/projects/api.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/projects/api.js +29 -0
- package/esm/src/server/handlers/dev/projects/html-shell.d.ts +2 -0
- package/esm/src/server/handlers/dev/projects/html-shell.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/projects/html-shell.js +40 -0
- package/esm/src/server/handlers/dev/projects/index.d.ts +10 -0
- package/esm/src/server/handlers/dev/projects/index.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/projects/index.js +52 -0
- package/esm/src/server/handlers/dev/projects/ui-handler.d.ts +3 -0
- package/esm/src/server/handlers/dev/projects/ui-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/projects/ui-handler.js +89 -0
- package/esm/src/server/handlers/dev/styles-css-handler.d.ts +21 -0
- package/esm/src/server/handlers/dev/styles-css-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/dev/styles-css-handler.js +163 -0
- package/esm/src/server/handlers/monitoring/client-log.d.ts +11 -0
- package/esm/src/server/handlers/monitoring/client-log.d.ts.map +1 -0
- package/esm/src/server/handlers/monitoring/client-log.js +73 -0
- package/esm/src/server/handlers/monitoring/health.d.ts +12 -0
- package/esm/src/server/handlers/monitoring/health.d.ts.map +1 -0
- package/esm/src/server/handlers/monitoring/health.js +80 -0
- package/esm/src/server/handlers/monitoring/memory.d.ts +16 -0
- package/esm/src/server/handlers/monitoring/memory.d.ts.map +1 -0
- package/esm/src/server/handlers/monitoring/memory.js +111 -0
- package/esm/src/server/handlers/monitoring/metrics.d.ts +9 -0
- package/esm/src/server/handlers/monitoring/metrics.d.ts.map +1 -0
- package/esm/src/server/handlers/monitoring/metrics.js +45 -0
- package/esm/src/server/handlers/preview/hmr-handler.d.ts +28 -0
- package/esm/src/server/handlers/preview/hmr-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/preview/hmr-handler.js +240 -0
- package/esm/src/server/handlers/preview/markdown-preview-handler.d.ts +17 -0
- package/esm/src/server/handlers/preview/markdown-preview-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/preview/markdown-preview-handler.js +238 -0
- package/esm/src/server/handlers/request/api/api-handler-wrapper.d.ts +46 -0
- package/esm/src/server/handlers/request/api/api-handler-wrapper.d.ts.map +1 -0
- package/esm/src/server/handlers/request/api/api-handler-wrapper.js +111 -0
- package/esm/src/server/handlers/request/api/app-router-handler.d.ts +9 -0
- package/esm/src/server/handlers/request/api/app-router-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/api/app-router-handler.js +53 -0
- package/esm/src/server/handlers/request/api/app-router-resolver.d.ts +10 -0
- package/esm/src/server/handlers/request/api/app-router-resolver.d.ts.map +1 -0
- package/esm/src/server/handlers/request/api/app-router-resolver.js +71 -0
- package/esm/src/server/handlers/request/api/index.d.ts +7 -0
- package/esm/src/server/handlers/request/api/index.d.ts.map +1 -0
- package/esm/src/server/handlers/request/api/index.js +5 -0
- package/esm/src/server/handlers/request/api/pages-api-handler.d.ts +10 -0
- package/esm/src/server/handlers/request/api/pages-api-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/api/pages-api-handler.js +51 -0
- package/esm/src/server/handlers/request/api/security-headers.d.ts +6 -0
- package/esm/src/server/handlers/request/api/security-headers.d.ts.map +1 -0
- package/esm/src/server/handlers/request/api/security-headers.js +12 -0
- package/esm/src/server/handlers/request/api/types.d.ts +11 -0
- package/esm/src/server/handlers/request/api/types.d.ts.map +1 -0
- package/esm/src/server/handlers/request/api/types.js +1 -0
- package/esm/src/server/handlers/request/css-handler.d.ts +8 -0
- package/esm/src/server/handlers/request/css-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/css-handler.js +59 -0
- package/esm/src/server/handlers/request/lib-modules-handler.d.ts +10 -0
- package/esm/src/server/handlers/request/lib-modules-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/lib-modules-handler.js +86 -0
- package/esm/src/server/handlers/request/module/batch-module-handler.d.ts +5 -0
- package/esm/src/server/handlers/request/module/batch-module-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/module/batch-module-handler.js +21 -0
- package/esm/src/server/handlers/request/module/data-endpoint-handler.d.ts +5 -0
- package/esm/src/server/handlers/request/module/data-endpoint-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/module/data-endpoint-handler.js +43 -0
- package/esm/src/server/handlers/request/module/index.d.ts +9 -0
- package/esm/src/server/handlers/request/module/index.d.ts.map +1 -0
- package/esm/src/server/handlers/request/module/index.js +8 -0
- package/esm/src/server/handlers/request/module/module-handler.d.ts +8 -0
- package/esm/src/server/handlers/request/module/module-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/module/module-handler.js +45 -0
- package/esm/src/server/handlers/request/module/module-server-handler.d.ts +5 -0
- package/esm/src/server/handlers/request/module/module-server-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/module/module-server-handler.js +34 -0
- package/esm/src/server/handlers/request/module/page-data-endpoint-handler.d.ts +5 -0
- package/esm/src/server/handlers/request/module/page-data-endpoint-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/module/page-data-endpoint-handler.js +52 -0
- package/esm/src/server/handlers/request/module/page-module-handler.d.ts +5 -0
- package/esm/src/server/handlers/request/module/page-module-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/module/page-module-handler.js +48 -0
- package/esm/src/server/handlers/request/module/virtual-module-handler.d.ts +5 -0
- package/esm/src/server/handlers/request/module/virtual-module-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/module/virtual-module-handler.js +33 -0
- package/esm/src/server/handlers/request/openapi-docs-handler.d.ts +18 -0
- package/esm/src/server/handlers/request/openapi-docs-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/openapi-docs-handler.js +67 -0
- package/esm/src/server/handlers/request/openapi-handler.d.ts +13 -0
- package/esm/src/server/handlers/request/openapi-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/openapi-handler.js +104 -0
- package/esm/src/server/handlers/request/rsc/endpoints/action-handler.d.ts +13 -0
- package/esm/src/server/handlers/request/rsc/endpoints/action-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/endpoints/action-handler.js +52 -0
- package/esm/src/server/handlers/request/rsc/endpoints/action-parser.d.ts +4 -0
- package/esm/src/server/handlers/request/rsc/endpoints/action-parser.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/endpoints/action-parser.js +37 -0
- package/esm/src/server/handlers/request/rsc/endpoints/endpoint-router.d.ts +13 -0
- package/esm/src/server/handlers/request/rsc/endpoints/endpoint-router.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/endpoints/endpoint-router.js +213 -0
- package/esm/src/server/handlers/request/rsc/endpoints/handler-registry.d.ts +9 -0
- package/esm/src/server/handlers/request/rsc/endpoints/handler-registry.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/endpoints/handler-registry.js +45 -0
- package/esm/src/server/handlers/request/rsc/endpoints/index.d.ts +7 -0
- package/esm/src/server/handlers/request/rsc/endpoints/index.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/endpoints/index.js +5 -0
- package/esm/src/server/handlers/request/rsc/endpoints/script-handlers.d.ts +7 -0
- package/esm/src/server/handlers/request/rsc/endpoints/script-handlers.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/endpoints/script-handlers.js +76 -0
- package/esm/src/server/handlers/request/rsc/endpoints/types.d.ts +24 -0
- package/esm/src/server/handlers/request/rsc/endpoints/types.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/endpoints/types.js +1 -0
- package/esm/src/server/handlers/request/rsc/handlers/component-resolver.d.ts +4 -0
- package/esm/src/server/handlers/request/rsc/handlers/component-resolver.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/handlers/component-resolver.js +58 -0
- package/esm/src/server/handlers/request/rsc/handlers/handler.d.ts +19 -0
- package/esm/src/server/handlers/request/rsc/handlers/handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/handlers/handler.js +52 -0
- package/esm/src/server/handlers/request/rsc/handlers/hydrator-handler.d.ts +13 -0
- package/esm/src/server/handlers/request/rsc/handlers/hydrator-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/handlers/hydrator-handler.js +91 -0
- package/esm/src/server/handlers/request/rsc/handlers/index.d.ts +3 -0
- package/esm/src/server/handlers/request/rsc/handlers/index.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/handlers/index.js +1 -0
- package/esm/src/server/handlers/request/rsc/handlers/manifest-handler.d.ts +12 -0
- package/esm/src/server/handlers/request/rsc/handlers/manifest-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/handlers/manifest-handler.js +34 -0
- package/esm/src/server/handlers/request/rsc/handlers/page-handler.d.ts +6 -0
- package/esm/src/server/handlers/request/rsc/handlers/page-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/handlers/page-handler.js +54 -0
- package/esm/src/server/handlers/request/rsc/handlers/render-handler.d.ts +17 -0
- package/esm/src/server/handlers/request/rsc/handlers/render-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/handlers/render-handler.js +107 -0
- package/esm/src/server/handlers/request/rsc/handlers/stream-handler.d.ts +10 -0
- package/esm/src/server/handlers/request/rsc/handlers/stream-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/handlers/stream-handler.js +79 -0
- package/esm/src/server/handlers/request/rsc/handlers/types.d.ts +30 -0
- package/esm/src/server/handlers/request/rsc/handlers/types.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/handlers/types.js +1 -0
- package/esm/src/server/handlers/request/rsc/index.d.ts +12 -0
- package/esm/src/server/handlers/request/rsc/index.d.ts.map +1 -0
- package/esm/src/server/handlers/request/rsc/index.js +55 -0
- package/esm/src/server/handlers/request/snippet-handler.d.ts +17 -0
- package/esm/src/server/handlers/request/snippet-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/snippet-handler.js +97 -0
- package/esm/src/server/handlers/request/ssr/error-page-fallback.d.ts +11 -0
- package/esm/src/server/handlers/request/ssr/error-page-fallback.d.ts.map +1 -0
- package/esm/src/server/handlers/request/ssr/error-page-fallback.js +148 -0
- package/esm/src/server/handlers/request/ssr/etag-handler.d.ts +4 -0
- package/esm/src/server/handlers/request/ssr/etag-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/ssr/etag-handler.js +16 -0
- package/esm/src/server/handlers/request/ssr/index.d.ts +5 -0
- package/esm/src/server/handlers/request/ssr/index.d.ts.map +1 -0
- package/esm/src/server/handlers/request/ssr/index.js +4 -0
- package/esm/src/server/handlers/request/ssr/not-found-fallback.d.ts +5 -0
- package/esm/src/server/handlers/request/ssr/not-found-fallback.d.ts.map +1 -0
- package/esm/src/server/handlers/request/ssr/not-found-fallback.js +63 -0
- package/esm/src/server/handlers/request/ssr/ssr-handler.d.ts +23 -0
- package/esm/src/server/handlers/request/ssr/ssr-handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/ssr/ssr-handler.js +339 -0
- package/esm/src/server/handlers/request/static.d.ts +21 -0
- package/esm/src/server/handlers/request/static.d.ts.map +1 -0
- package/esm/src/server/handlers/request/static.js +236 -0
- package/esm/src/server/handlers/response/base.d.ts +2 -0
- package/esm/src/server/handlers/response/base.d.ts.map +1 -0
- package/esm/src/server/handlers/response/base.js +1 -0
- package/esm/src/server/handlers/response/cors.d.ts +13 -0
- package/esm/src/server/handlers/response/cors.d.ts.map +1 -0
- package/esm/src/server/handlers/response/cors.js +124 -0
- package/esm/src/server/handlers/response/not-found.d.ts +9 -0
- package/esm/src/server/handlers/response/not-found.d.ts.map +1 -0
- package/esm/src/server/handlers/response/not-found.js +138 -0
- package/esm/src/server/handlers/studio/endpoints.d.ts +12 -0
- package/esm/src/server/handlers/studio/endpoints.d.ts.map +1 -0
- package/esm/src/server/handlers/studio/endpoints.js +27 -0
- package/esm/src/server/handlers/types.d.ts +8 -0
- package/esm/src/server/handlers/types.d.ts.map +1 -0
- package/esm/src/server/handlers/types.js +7 -0
- package/esm/src/server/handlers/utils/content-types.d.ts +6 -0
- package/esm/src/server/handlers/utils/content-types.d.ts.map +1 -0
- package/esm/src/server/handlers/utils/content-types.js +104 -0
- package/esm/src/server/handlers/utils/etag.d.ts +7 -0
- package/esm/src/server/handlers/utils/etag.d.ts.map +1 -0
- package/esm/src/server/handlers/utils/etag.js +36 -0
- package/esm/src/server/index.d.ts +8 -0
- package/esm/src/server/index.d.ts.map +1 -0
- package/esm/src/server/index.js +12 -0
- package/esm/src/server/production-server.d.ts +27 -0
- package/esm/src/server/production-server.d.ts.map +1 -0
- package/esm/src/server/production-server.js +169 -0
- package/esm/src/server/reload-notifier.d.ts +37 -0
- package/esm/src/server/reload-notifier.d.ts.map +1 -0
- package/esm/src/server/reload-notifier.js +123 -0
- package/esm/src/server/shared/renderer/adapter.d.ts +33 -0
- package/esm/src/server/shared/renderer/adapter.d.ts.map +1 -0
- package/esm/src/server/shared/renderer/adapter.js +195 -0
- package/esm/src/server/shared/renderer/index.d.ts +3 -0
- package/esm/src/server/shared/renderer/index.d.ts.map +1 -0
- package/esm/src/server/shared/renderer/index.js +2 -0
- package/esm/src/server/shared/renderer/memory/pressure.d.ts +19 -0
- package/esm/src/server/shared/renderer/memory/pressure.d.ts.map +1 -0
- package/esm/src/server/shared/renderer/memory/pressure.js +47 -0
- package/esm/src/server/shared/renderer-factory.d.ts +9 -0
- package/esm/src/server/shared/renderer-factory.d.ts.map +1 -0
- package/esm/src/server/shared/renderer-factory.js +8 -0
- package/esm/src/server/universal-handler/index.d.ts +44 -0
- package/esm/src/server/universal-handler/index.d.ts.map +1 -0
- package/esm/src/server/universal-handler/index.js +788 -0
- package/esm/src/server/universal-handler/project-isolation.d.ts +32 -0
- package/esm/src/server/universal-handler/project-isolation.d.ts.map +1 -0
- package/esm/src/server/universal-handler/project-isolation.js +139 -0
- package/esm/src/server/universal-handler/proxy-environment.d.ts +9 -0
- package/esm/src/server/universal-handler/proxy-environment.d.ts.map +1 -0
- package/esm/src/server/universal-handler/proxy-environment.js +13 -0
- package/esm/src/server/universal-handler/request-tracker.d.ts +41 -0
- package/esm/src/server/universal-handler/request-tracker.d.ts.map +1 -0
- package/esm/src/server/universal-handler/request-tracker.js +203 -0
- package/esm/src/server/utils/domain-lookup.d.ts +22 -0
- package/esm/src/server/utils/domain-lookup.d.ts.map +1 -0
- package/esm/src/server/utils/domain-lookup.js +134 -0
- package/esm/src/server/utils/domain-parser.d.ts +25 -0
- package/esm/src/server/utils/domain-parser.d.ts.map +1 -0
- package/esm/src/server/utils/domain-parser.js +116 -0
- package/esm/src/server/utils/error-html.d.ts +30 -0
- package/esm/src/server/utils/error-html.d.ts.map +1 -0
- package/esm/src/server/utils/error-html.js +128 -0
- package/esm/src/studio/bridge-template.d.ts +7 -0
- package/esm/src/studio/bridge-template.d.ts.map +1 -0
- package/esm/src/studio/bridge-template.js +733 -0
- package/esm/src/studio/element-selector-injector.d.ts +14 -0
- package/esm/src/studio/element-selector-injector.d.ts.map +1 -0
- package/esm/src/studio/element-selector-injector.js +75 -0
- package/esm/src/studio/hash-utils.d.ts +6 -0
- package/esm/src/studio/hash-utils.d.ts.map +1 -0
- package/esm/src/studio/hash-utils.js +12 -0
- package/esm/src/tool/executor.d.ts +3 -0
- package/esm/src/tool/executor.d.ts.map +1 -0
- package/esm/src/tool/executor.js +12 -0
- package/esm/src/tool/factory.d.ts +16 -0
- package/esm/src/tool/factory.d.ts.map +1 -0
- package/esm/src/tool/factory.js +122 -0
- package/esm/src/tool/index.d.ts +9 -0
- package/esm/src/tool/index.d.ts.map +1 -0
- package/esm/src/tool/index.js +5 -0
- package/esm/src/tool/registry.d.ts +15 -0
- package/esm/src/tool/registry.d.ts.map +1 -0
- package/esm/src/tool/registry.js +43 -0
- package/esm/src/tool/schema/index.d.ts +4 -0
- package/esm/src/tool/schema/index.d.ts.map +1 -0
- package/esm/src/tool/schema/index.js +2 -0
- package/esm/src/tool/schema/json-schema.d.ts +16 -0
- package/esm/src/tool/schema/json-schema.d.ts.map +1 -0
- package/esm/src/tool/schema/json-schema.js +1 -0
- package/esm/src/tool/schema/zod-json-schema.d.ts +5 -0
- package/esm/src/tool/schema/zod-json-schema.d.ts.map +1 -0
- package/esm/src/tool/schema/zod-json-schema.js +128 -0
- package/esm/src/tool/testing/index.d.ts +4 -0
- package/esm/src/tool/testing/index.d.ts.map +1 -0
- package/esm/src/tool/testing/index.js +2 -0
- package/esm/src/tool/testing/tool-tester.d.ts +35 -0
- package/esm/src/tool/testing/tool-tester.d.ts.map +1 -0
- package/esm/src/tool/testing/tool-tester.js +138 -0
- package/esm/src/tool/types.d.ts +102 -0
- package/esm/src/tool/types.d.ts.map +1 -0
- package/esm/src/tool/types.js +4 -0
- package/esm/src/transforms/esm/http-bundler.d.ts +24 -0
- package/esm/src/transforms/esm/http-bundler.d.ts.map +1 -0
- package/esm/src/transforms/esm/http-bundler.js +178 -0
- package/esm/src/transforms/esm/http-cache.d.ts +54 -0
- package/esm/src/transforms/esm/http-cache.d.ts.map +1 -0
- package/esm/src/transforms/esm/http-cache.js +471 -0
- package/esm/src/transforms/esm/import-parser.d.ts +23 -0
- package/esm/src/transforms/esm/import-parser.d.ts.map +1 -0
- package/esm/src/transforms/esm/import-parser.js +187 -0
- package/esm/src/transforms/esm/import-rewriter.d.ts +4 -0
- package/esm/src/transforms/esm/import-rewriter.d.ts.map +1 -0
- package/esm/src/transforms/esm/import-rewriter.js +144 -0
- package/esm/src/transforms/esm/index.d.ts +8 -0
- package/esm/src/transforms/esm/index.d.ts.map +1 -0
- package/esm/src/transforms/esm/index.js +5 -0
- package/esm/src/transforms/esm/lexer.d.ts +22 -0
- package/esm/src/transforms/esm/lexer.d.ts.map +1 -0
- package/esm/src/transforms/esm/lexer.js +129 -0
- package/esm/src/transforms/esm/package-registry.d.ts +81 -0
- package/esm/src/transforms/esm/package-registry.d.ts.map +1 -0
- package/esm/src/transforms/esm/package-registry.js +121 -0
- package/esm/src/transforms/esm/path-resolver.d.ts +24 -0
- package/esm/src/transforms/esm/path-resolver.d.ts.map +1 -0
- package/esm/src/transforms/esm/path-resolver.js +189 -0
- package/esm/src/transforms/esm/react-imports.d.ts +3 -0
- package/esm/src/transforms/esm/react-imports.d.ts.map +1 -0
- package/esm/src/transforms/esm/react-imports.js +38 -0
- package/esm/src/transforms/esm/transform-cache.d.ts +23 -0
- package/esm/src/transforms/esm/transform-cache.d.ts.map +1 -0
- package/esm/src/transforms/esm/transform-cache.js +119 -0
- package/esm/src/transforms/esm/transform-utils.d.ts +11 -0
- package/esm/src/transforms/esm/transform-utils.d.ts.map +1 -0
- package/esm/src/transforms/esm/transform-utils.js +28 -0
- package/esm/src/transforms/esm/types.d.ts +19 -0
- package/esm/src/transforms/esm/types.d.ts.map +1 -0
- package/esm/src/transforms/esm/types.js +1 -0
- package/esm/src/transforms/esm-transform.d.ts +3 -0
- package/esm/src/transforms/esm-transform.d.ts.map +1 -0
- package/esm/src/transforms/esm-transform.js +1 -0
- package/esm/src/transforms/md/compiler/index.d.ts +2 -0
- package/esm/src/transforms/md/compiler/index.d.ts.map +1 -0
- package/esm/src/transforms/md/compiler/index.js +1 -0
- package/esm/src/transforms/md/compiler/md-compiler.d.ts +3 -0
- package/esm/src/transforms/md/compiler/md-compiler.d.ts.map +1 -0
- package/esm/src/transforms/md/compiler/md-compiler.js +100 -0
- package/esm/src/transforms/md/utils.d.ts +19 -0
- package/esm/src/transforms/md/utils.d.ts.map +1 -0
- package/esm/src/transforms/md/utils.js +31 -0
- package/esm/src/transforms/mdx/compiler/frontmatter-extractor.d.ts +6 -0
- package/esm/src/transforms/mdx/compiler/frontmatter-extractor.d.ts.map +1 -0
- package/esm/src/transforms/mdx/compiler/frontmatter-extractor.js +57 -0
- package/esm/src/transforms/mdx/compiler/import-rewriter.d.ts +10 -0
- package/esm/src/transforms/mdx/compiler/import-rewriter.d.ts.map +1 -0
- package/esm/src/transforms/mdx/compiler/import-rewriter.js +89 -0
- package/esm/src/transforms/mdx/compiler/index.d.ts +9 -0
- package/esm/src/transforms/mdx/compiler/index.d.ts.map +1 -0
- package/esm/src/transforms/mdx/compiler/index.js +16 -0
- package/esm/src/transforms/mdx/compiler/mdx-compiler.d.ts +3 -0
- package/esm/src/transforms/mdx/compiler/mdx-compiler.d.ts.map +1 -0
- package/esm/src/transforms/mdx/compiler/mdx-compiler.js +68 -0
- package/esm/src/transforms/mdx/compiler/types.d.ts +16 -0
- package/esm/src/transforms/mdx/compiler/types.d.ts.map +1 -0
- package/esm/src/transforms/mdx/compiler/types.js +1 -0
- package/esm/src/transforms/mdx/esm-module-loader/cache/index.d.ts +38 -0
- package/esm/src/transforms/mdx/esm-module-loader/cache/index.d.ts.map +1 -0
- package/esm/src/transforms/mdx/esm-module-loader/cache/index.js +130 -0
- package/esm/src/transforms/mdx/esm-module-loader/constants.d.ts +18 -0
- package/esm/src/transforms/mdx/esm-module-loader/constants.d.ts.map +1 -0
- package/esm/src/transforms/mdx/esm-module-loader/constants.js +18 -0
- package/esm/src/transforms/mdx/esm-module-loader/index.d.ts +6 -0
- package/esm/src/transforms/mdx/esm-module-loader/index.d.ts.map +1 -0
- package/esm/src/transforms/mdx/esm-module-loader/index.js +4 -0
- package/esm/src/transforms/mdx/esm-module-loader/loader.d.ts +12 -0
- package/esm/src/transforms/mdx/esm-module-loader/loader.d.ts.map +1 -0
- package/esm/src/transforms/mdx/esm-module-loader/loader.js +459 -0
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.d.ts +24 -0
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.d.ts.map +1 -0
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/index.js +572 -0
- package/esm/src/transforms/mdx/esm-module-loader/resolution/file-finder.d.ts +12 -0
- package/esm/src/transforms/mdx/esm-module-loader/resolution/file-finder.d.ts.map +1 -0
- package/esm/src/transforms/mdx/esm-module-loader/resolution/file-finder.js +131 -0
- package/esm/src/transforms/mdx/esm-module-loader/types.d.ts +57 -0
- package/esm/src/transforms/mdx/esm-module-loader/types.d.ts.map +1 -0
- package/esm/src/transforms/mdx/esm-module-loader/types.js +1 -0
- package/esm/src/transforms/mdx/esm-module-loader/utils/hash.d.ts +2 -0
- package/esm/src/transforms/mdx/esm-module-loader/utils/hash.d.ts.map +1 -0
- package/esm/src/transforms/mdx/esm-module-loader/utils/hash.js +9 -0
- package/esm/src/transforms/mdx/esm-module-loader/utils/stub-module.d.ts +4 -0
- package/esm/src/transforms/mdx/esm-module-loader/utils/stub-module.d.ts.map +1 -0
- package/esm/src/transforms/mdx/esm-module-loader/utils/stub-module.js +56 -0
- package/esm/src/transforms/mdx/index.d.ts +20 -0
- package/esm/src/transforms/mdx/index.d.ts.map +1 -0
- package/esm/src/transforms/mdx/index.js +82 -0
- package/esm/src/transforms/mdx/mdx-cache-adapter.d.ts +35 -0
- package/esm/src/transforms/mdx/mdx-cache-adapter.d.ts.map +1 -0
- package/esm/src/transforms/mdx/mdx-cache-adapter.js +136 -0
- package/esm/src/transforms/mdx/module-loader/metadata-extractor.d.ts +5 -0
- package/esm/src/transforms/mdx/module-loader/metadata-extractor.d.ts.map +1 -0
- package/esm/src/transforms/mdx/module-loader/metadata-extractor.js +98 -0
- package/esm/src/transforms/mdx/module-loader/string-parser.d.ts +4 -0
- package/esm/src/transforms/mdx/module-loader/string-parser.d.ts.map +1 -0
- package/esm/src/transforms/mdx/module-loader/string-parser.js +45 -0
- package/esm/src/transforms/mdx/module-loader/types.d.ts +52 -0
- package/esm/src/transforms/mdx/module-loader/types.d.ts.map +1 -0
- package/esm/src/transforms/mdx/module-loader/types.js +1 -0
- package/esm/src/transforms/mdx/parser.d.ts +4 -0
- package/esm/src/transforms/mdx/parser.d.ts.map +1 -0
- package/esm/src/transforms/mdx/parser.js +49 -0
- package/esm/src/transforms/mdx/types.d.ts +95 -0
- package/esm/src/transforms/mdx/types.d.ts.map +1 -0
- package/esm/src/transforms/mdx/types.js +1 -0
- package/esm/src/transforms/pipeline/context.d.ts +12 -0
- package/esm/src/transforms/pipeline/context.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/context.js +94 -0
- package/esm/src/transforms/pipeline/index.d.ts +8 -0
- package/esm/src/transforms/pipeline/index.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/index.js +96 -0
- package/esm/src/transforms/pipeline/stages/compile.d.ts +12 -0
- package/esm/src/transforms/pipeline/stages/compile.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/compile.js +59 -0
- package/esm/src/transforms/pipeline/stages/finalize.d.ts +4 -0
- package/esm/src/transforms/pipeline/stages/finalize.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/finalize.js +15 -0
- package/esm/src/transforms/pipeline/stages/index.d.ts +11 -0
- package/esm/src/transforms/pipeline/stages/index.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/index.js +10 -0
- package/esm/src/transforms/pipeline/stages/parse.d.ts +4 -0
- package/esm/src/transforms/pipeline/stages/parse.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/parse.js +18 -0
- package/esm/src/transforms/pipeline/stages/resolve-aliases.d.ts +4 -0
- package/esm/src/transforms/pipeline/stages/resolve-aliases.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/resolve-aliases.js +16 -0
- package/esm/src/transforms/pipeline/stages/resolve-bare.d.ts +4 -0
- package/esm/src/transforms/pipeline/stages/resolve-bare.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/resolve-bare.js +24 -0
- package/esm/src/transforms/pipeline/stages/resolve-context.d.ts +4 -0
- package/esm/src/transforms/pipeline/stages/resolve-context.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/resolve-context.js +9 -0
- package/esm/src/transforms/pipeline/stages/resolve-react.d.ts +4 -0
- package/esm/src/transforms/pipeline/stages/resolve-react.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/resolve-react.js +17 -0
- package/esm/src/transforms/pipeline/stages/resolve-relative.d.ts +4 -0
- package/esm/src/transforms/pipeline/stages/resolve-relative.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/resolve-relative.js +27 -0
- package/esm/src/transforms/pipeline/stages/ssr-http-cache.d.ts +11 -0
- package/esm/src/transforms/pipeline/stages/ssr-http-cache.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/ssr-http-cache.js +40 -0
- package/esm/src/transforms/pipeline/stages/ssr-http-stub.d.ts +7 -0
- package/esm/src/transforms/pipeline/stages/ssr-http-stub.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/ssr-http-stub.js +79 -0
- package/esm/src/transforms/pipeline/types.d.ts +135 -0
- package/esm/src/transforms/pipeline/types.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/types.js +29 -0
- package/esm/src/transforms/plugins/babel-node-positions.d.ts +16 -0
- package/esm/src/transforms/plugins/babel-node-positions.d.ts.map +1 -0
- package/esm/src/transforms/plugins/babel-node-positions.js +131 -0
- package/esm/src/transforms/plugins/plugin-loader.d.ts +7 -0
- package/esm/src/transforms/plugins/plugin-loader.d.ts.map +1 -0
- package/esm/src/transforms/plugins/plugin-loader.js +21 -0
- package/esm/src/transforms/plugins/remark-headings.d.ts +4 -0
- package/esm/src/transforms/plugins/remark-headings.d.ts.map +1 -0
- package/esm/src/transforms/plugins/remark-headings.js +82 -0
- package/esm/src/transforms/plugins/remark-mdx-utils.d.ts +14 -0
- package/esm/src/transforms/plugins/remark-mdx-utils.d.ts.map +1 -0
- package/esm/src/transforms/plugins/remark-mdx-utils.js +195 -0
- package/esm/src/types/app.d.ts +6 -0
- package/esm/src/types/app.d.ts.map +1 -0
- package/esm/src/types/app.js +1 -0
- package/esm/src/types/branded.d.ts +26 -0
- package/esm/src/types/branded.d.ts.map +1 -0
- package/esm/src/types/branded.js +9 -0
- package/esm/src/types/bundler.d.ts +53 -0
- package/esm/src/types/bundler.d.ts.map +1 -0
- package/esm/src/types/bundler.js +1 -0
- package/esm/src/types/entities/getEntityInfo.d.ts +6 -0
- package/esm/src/types/entities/getEntityInfo.d.ts.map +1 -0
- package/esm/src/types/entities/getEntityInfo.js +302 -0
- package/esm/src/types/entities.d.ts +71 -0
- package/esm/src/types/entities.d.ts.map +1 -0
- package/esm/src/types/entities.js +85 -0
- package/esm/src/types/global-guards.d.ts +11 -0
- package/esm/src/types/global-guards.d.ts.map +1 -0
- package/esm/src/types/global-guards.js +13 -0
- package/esm/src/types/hmr.d.ts +17 -0
- package/esm/src/types/hmr.d.ts.map +1 -0
- package/esm/src/types/hmr.js +1 -0
- package/esm/src/types/index.d.ts +126 -0
- package/esm/src/types/index.d.ts.map +1 -0
- package/esm/src/types/index.js +2 -0
- package/esm/src/types/rsc.d.ts +40 -0
- package/esm/src/types/rsc.d.ts.map +1 -0
- package/esm/src/types/rsc.js +1 -0
- package/esm/src/types/server.d.ts +139 -0
- package/esm/src/types/server.d.ts.map +1 -0
- package/esm/src/types/server.js +8 -0
- package/esm/src/utils/base64url.d.ts +6 -0
- package/esm/src/utils/base64url.d.ts.map +1 -0
- package/esm/src/utils/base64url.js +12 -0
- package/esm/src/utils/bundle-manifest-init.d.ts +7 -0
- package/esm/src/utils/bundle-manifest-init.d.ts.map +1 -0
- package/esm/src/utils/bundle-manifest-init.js +91 -0
- package/esm/src/utils/bundle-manifest-kv.d.ts +31 -0
- package/esm/src/utils/bundle-manifest-kv.d.ts.map +1 -0
- package/esm/src/utils/bundle-manifest-kv.js +43 -0
- package/esm/src/utils/bundle-manifest-redis.d.ts +32 -0
- package/esm/src/utils/bundle-manifest-redis.d.ts.map +1 -0
- package/esm/src/utils/bundle-manifest-redis.js +43 -0
- package/esm/src/utils/bundle-manifest.d.ts +64 -0
- package/esm/src/utils/bundle-manifest.d.ts.map +1 -0
- package/esm/src/utils/bundle-manifest.js +100 -0
- package/esm/src/utils/cache/eviction/eviction-manager.d.ts +37 -0
- package/esm/src/utils/cache/eviction/eviction-manager.d.ts.map +1 -0
- package/esm/src/utils/cache/eviction/eviction-manager.js +84 -0
- package/esm/src/utils/cache/stores/memory/entry-manager.d.ts +12 -0
- package/esm/src/utils/cache/stores/memory/entry-manager.d.ts.map +1 -0
- package/esm/src/utils/cache/stores/memory/entry-manager.js +67 -0
- package/esm/src/utils/cache/stores/memory/lru-cache-adapter.d.ts +24 -0
- package/esm/src/utils/cache/stores/memory/lru-cache-adapter.d.ts.map +1 -0
- package/esm/src/utils/cache/stores/memory/lru-cache-adapter.js +158 -0
- package/esm/src/utils/cache/stores/memory/lru-list-manager.d.ts +12 -0
- package/esm/src/utils/cache/stores/memory/lru-list-manager.d.ts.map +1 -0
- package/esm/src/utils/cache/stores/memory/lru-list-manager.js +48 -0
- package/esm/src/utils/cache/stores/memory/lru-node.d.ts +9 -0
- package/esm/src/utils/cache/stores/memory/lru-node.d.ts.map +1 -0
- package/esm/src/utils/cache/stores/memory/lru-node.js +12 -0
- package/esm/src/utils/cache/stores/memory/types.d.ts +30 -0
- package/esm/src/utils/cache/stores/memory/types.d.ts.map +1 -0
- package/esm/src/utils/cache/stores/memory/types.js +1 -0
- package/esm/src/utils/cache-dir.d.ts +6 -0
- package/esm/src/utils/cache-dir.d.ts.map +1 -0
- package/esm/src/utils/cache-dir.js +26 -0
- package/esm/src/utils/chunk-utils.d.ts +3 -0
- package/esm/src/utils/chunk-utils.d.ts.map +1 -0
- package/esm/src/utils/chunk-utils.js +15 -0
- package/esm/src/utils/circuit-breaker.d.ts +44 -0
- package/esm/src/utils/circuit-breaker.d.ts.map +1 -0
- package/esm/src/utils/circuit-breaker.js +108 -0
- package/esm/src/utils/constants/buffers.d.ts +25 -0
- package/esm/src/utils/constants/buffers.d.ts.map +1 -0
- package/esm/src/utils/constants/buffers.js +24 -0
- package/esm/src/utils/constants/build.d.ts +6 -0
- package/esm/src/utils/constants/build.d.ts.map +1 -0
- package/esm/src/utils/constants/build.js +5 -0
- package/esm/src/utils/constants/cache.d.ts +54 -0
- package/esm/src/utils/constants/cache.d.ts.map +1 -0
- package/esm/src/utils/constants/cache.js +97 -0
- package/esm/src/utils/constants/cdn.d.ts +23 -0
- package/esm/src/utils/constants/cdn.d.ts.map +1 -0
- package/esm/src/utils/constants/cdn.js +53 -0
- package/esm/src/utils/constants/crypto.d.ts +10 -0
- package/esm/src/utils/constants/crypto.d.ts.map +1 -0
- package/esm/src/utils/constants/crypto.js +9 -0
- package/esm/src/utils/constants/env.d.ts +16 -0
- package/esm/src/utils/constants/env.d.ts.map +1 -0
- package/esm/src/utils/constants/env.js +22 -0
- package/esm/src/utils/constants/hash.d.ts +3 -0
- package/esm/src/utils/constants/hash.d.ts.map +1 -0
- package/esm/src/utils/constants/hash.js +2 -0
- package/esm/src/utils/constants/hmr.d.ts +17 -0
- package/esm/src/utils/constants/hmr.d.ts.map +1 -0
- package/esm/src/utils/constants/hmr.js +19 -0
- package/esm/src/utils/constants/html.d.ts +8 -0
- package/esm/src/utils/constants/html.d.ts.map +1 -0
- package/esm/src/utils/constants/html.js +7 -0
- package/esm/src/utils/constants/http.d.ts +52 -0
- package/esm/src/utils/constants/http.d.ts.map +1 -0
- package/esm/src/utils/constants/http.js +51 -0
- package/esm/src/utils/constants/index.d.ts +18 -0
- package/esm/src/utils/constants/index.d.ts.map +1 -0
- package/esm/src/utils/constants/index.js +17 -0
- package/esm/src/utils/constants/limits.d.ts +25 -0
- package/esm/src/utils/constants/limits.d.ts.map +1 -0
- package/esm/src/utils/constants/limits.js +24 -0
- package/esm/src/utils/constants/metrics.d.ts +10 -0
- package/esm/src/utils/constants/metrics.d.ts.map +1 -0
- package/esm/src/utils/constants/metrics.js +34 -0
- package/esm/src/utils/constants/network.d.ts +20 -0
- package/esm/src/utils/constants/network.d.ts.map +1 -0
- package/esm/src/utils/constants/network.js +24 -0
- package/esm/src/utils/constants/priorities.d.ts +31 -0
- package/esm/src/utils/constants/priorities.d.ts.map +1 -0
- package/esm/src/utils/constants/priorities.js +30 -0
- package/esm/src/utils/constants/retry.d.ts +17 -0
- package/esm/src/utils/constants/retry.d.ts.map +1 -0
- package/esm/src/utils/constants/retry.js +16 -0
- package/esm/src/utils/constants/security.d.ts +7 -0
- package/esm/src/utils/constants/security.d.ts.map +1 -0
- package/esm/src/utils/constants/security.js +6 -0
- package/esm/src/utils/constants/server.d.ts +109 -0
- package/esm/src/utils/constants/server.d.ts.map +1 -0
- package/esm/src/utils/constants/server.js +122 -0
- package/esm/src/utils/cookie-utils.d.ts +6 -0
- package/esm/src/utils/cookie-utils.d.ts.map +1 -0
- package/esm/src/utils/cookie-utils.js +23 -0
- package/esm/src/utils/env-loader.d.ts +7 -0
- package/esm/src/utils/env-loader.d.ts.map +1 -0
- package/esm/src/utils/env-loader.js +114 -0
- package/esm/src/utils/feature-flags.d.ts +7 -0
- package/esm/src/utils/feature-flags.d.ts.map +1 -0
- package/esm/src/utils/feature-flags.js +4 -0
- package/esm/src/utils/file-discovery.d.ts +30 -0
- package/esm/src/utils/file-discovery.d.ts.map +1 -0
- package/esm/src/utils/file-discovery.js +147 -0
- package/esm/src/utils/format-utils.d.ts +7 -0
- package/esm/src/utils/format-utils.d.ts.map +1 -0
- package/esm/src/utils/format-utils.js +85 -0
- package/esm/src/utils/hash-utils.d.ts +14 -0
- package/esm/src/utils/hash-utils.d.ts.map +1 -0
- package/esm/src/utils/hash-utils.js +27 -0
- package/esm/src/utils/id.d.ts +9 -0
- package/esm/src/utils/id.d.ts.map +1 -0
- package/esm/src/utils/id.js +27 -0
- package/esm/src/utils/import-lockfile.d.ts +50 -0
- package/esm/src/utils/import-lockfile.d.ts.map +1 -0
- package/esm/src/utils/import-lockfile.js +175 -0
- package/esm/src/utils/index.d.ts +18 -0
- package/esm/src/utils/index.d.ts.map +1 -0
- package/esm/src/utils/index.js +17 -0
- package/esm/src/utils/logger/env.d.ts +5 -0
- package/esm/src/utils/logger/env.d.ts.map +1 -0
- package/esm/src/utils/logger/env.js +27 -0
- package/esm/src/utils/logger/index.d.ts +3 -0
- package/esm/src/utils/logger/index.d.ts.map +1 -0
- package/esm/src/utils/logger/index.js +2 -0
- package/esm/src/utils/logger/logger.d.ts +89 -0
- package/esm/src/utils/logger/logger.d.ts.map +1 -0
- package/esm/src/utils/logger/logger.js +339 -0
- package/esm/src/utils/lru-wrapper.d.ts +25 -0
- package/esm/src/utils/lru-wrapper.d.ts.map +1 -0
- package/esm/src/utils/lru-wrapper.js +87 -0
- package/esm/src/utils/memoize.d.ts +16 -0
- package/esm/src/utils/memoize.d.ts.map +1 -0
- package/esm/src/utils/memoize.js +59 -0
- package/esm/src/utils/memory/index.d.ts +2 -0
- package/esm/src/utils/memory/index.d.ts.map +1 -0
- package/esm/src/utils/memory/index.js +1 -0
- package/esm/src/utils/memory/profiler.d.ts +45 -0
- package/esm/src/utils/memory/profiler.d.ts.map +1 -0
- package/esm/src/utils/memory/profiler.js +151 -0
- package/esm/src/utils/parallel.d.ts +24 -0
- package/esm/src/utils/parallel.d.ts.map +1 -0
- package/esm/src/utils/parallel.js +84 -0
- package/esm/src/utils/path-utils.d.ts +14 -0
- package/esm/src/utils/path-utils.d.ts.map +1 -0
- package/esm/src/utils/path-utils.js +70 -0
- package/esm/src/utils/paths.d.ts +17 -0
- package/esm/src/utils/paths.d.ts.map +1 -0
- package/esm/src/utils/paths.js +16 -0
- package/esm/src/utils/perf-timer.d.ts +12 -0
- package/esm/src/utils/perf-timer.d.ts.map +1 -0
- package/esm/src/utils/perf-timer.js +84 -0
- package/esm/src/utils/platform.d.ts +9 -0
- package/esm/src/utils/platform.d.ts.map +1 -0
- package/esm/src/utils/platform.js +19 -0
- package/esm/src/utils/redis-client.d.ts +38 -0
- package/esm/src/utils/redis-client.d.ts.map +1 -0
- package/esm/src/utils/redis-client.js +95 -0
- package/esm/src/utils/route-path-utils.d.ts +86 -0
- package/esm/src/utils/route-path-utils.d.ts.map +1 -0
- package/esm/src/utils/route-path-utils.js +165 -0
- package/esm/src/utils/runtime-guards.d.ts +23 -0
- package/esm/src/utils/runtime-guards.d.ts.map +1 -0
- package/esm/src/utils/runtime-guards.js +18 -0
- package/esm/src/utils/semaphore.d.ts +24 -0
- package/esm/src/utils/semaphore.d.ts.map +1 -0
- package/esm/src/utils/semaphore.js +80 -0
- package/esm/src/utils/singleflight.d.ts +7 -0
- package/esm/src/utils/singleflight.d.ts.map +1 -0
- package/esm/src/utils/singleflight.js +22 -0
- package/esm/src/utils/version.d.ts +9 -0
- package/esm/src/utils/version.d.ts.map +1 -0
- package/esm/src/utils/version.js +12 -0
- package/esm/src/workflow/api/index.d.ts +6 -0
- package/esm/src/workflow/api/index.d.ts.map +1 -0
- package/esm/src/workflow/api/index.js +4 -0
- package/esm/src/workflow/api/workflow-client.d.ts +54 -0
- package/esm/src/workflow/api/workflow-client.d.ts.map +1 -0
- package/esm/src/workflow/api/workflow-client.js +118 -0
- package/esm/src/workflow/backends/cloudflare.d.ts +33 -0
- package/esm/src/workflow/backends/cloudflare.d.ts.map +1 -0
- package/esm/src/workflow/backends/cloudflare.js +61 -0
- package/esm/src/workflow/backends/inngest.d.ts +26 -0
- package/esm/src/workflow/backends/inngest.d.ts.map +1 -0
- package/esm/src/workflow/backends/inngest.js +47 -0
- package/esm/src/workflow/backends/memory.d.ts +61 -0
- package/esm/src/workflow/backends/memory.d.ts.map +1 -0
- package/esm/src/workflow/backends/memory.js +296 -0
- package/esm/src/workflow/backends/redis/index.d.ts +56 -0
- package/esm/src/workflow/backends/redis/index.d.ts.map +1 -0
- package/esm/src/workflow/backends/redis/index.js +468 -0
- package/esm/src/workflow/backends/redis/types.d.ts +40 -0
- package/esm/src/workflow/backends/redis/types.d.ts.map +1 -0
- package/esm/src/workflow/backends/redis/types.js +8 -0
- package/esm/src/workflow/backends/redis.d.ts +10 -0
- package/esm/src/workflow/backends/redis.d.ts.map +1 -0
- package/esm/src/workflow/backends/redis.js +8 -0
- package/esm/src/workflow/backends/temporal.d.ts +43 -0
- package/esm/src/workflow/backends/temporal.d.ts.map +1 -0
- package/esm/src/workflow/backends/temporal.js +65 -0
- package/esm/src/workflow/backends/types.d.ts +62 -0
- package/esm/src/workflow/backends/types.d.ts.map +1 -0
- package/esm/src/workflow/backends/types.js +13 -0
- package/esm/src/workflow/blob/gcs-storage.d.ts +31 -0
- package/esm/src/workflow/blob/gcs-storage.d.ts.map +1 -0
- package/esm/src/workflow/blob/gcs-storage.js +221 -0
- package/esm/src/workflow/blob/index.d.ts +8 -0
- package/esm/src/workflow/blob/index.d.ts.map +1 -0
- package/esm/src/workflow/blob/index.js +6 -0
- package/esm/src/workflow/blob/local-storage.d.ts +27 -0
- package/esm/src/workflow/blob/local-storage.d.ts.map +1 -0
- package/esm/src/workflow/blob/local-storage.js +146 -0
- package/esm/src/workflow/blob/s3-storage.d.ts +50 -0
- package/esm/src/workflow/blob/s3-storage.d.ts.map +1 -0
- package/esm/src/workflow/blob/s3-storage.js +246 -0
- package/esm/src/workflow/blob/types.d.ts +32 -0
- package/esm/src/workflow/blob/types.d.ts.map +1 -0
- package/esm/src/workflow/blob/types.js +1 -0
- package/esm/src/workflow/dsl/branch.d.ts +17 -0
- package/esm/src/workflow/dsl/branch.d.ts.map +1 -0
- package/esm/src/workflow/dsl/branch.js +40 -0
- package/esm/src/workflow/dsl/index.d.ts +17 -0
- package/esm/src/workflow/dsl/index.d.ts.map +1 -0
- package/esm/src/workflow/dsl/index.js +8 -0
- package/esm/src/workflow/dsl/loop.d.ts +41 -0
- package/esm/src/workflow/dsl/loop.d.ts.map +1 -0
- package/esm/src/workflow/dsl/loop.js +54 -0
- package/esm/src/workflow/dsl/map.d.ts +12 -0
- package/esm/src/workflow/dsl/map.d.ts.map +1 -0
- package/esm/src/workflow/dsl/map.js +21 -0
- package/esm/src/workflow/dsl/parallel.d.ts +11 -0
- package/esm/src/workflow/dsl/parallel.d.ts.map +1 -0
- package/esm/src/workflow/dsl/parallel.js +26 -0
- package/esm/src/workflow/dsl/step.d.ts +16 -0
- package/esm/src/workflow/dsl/step.d.ts.map +1 -0
- package/esm/src/workflow/dsl/step.js +29 -0
- package/esm/src/workflow/dsl/sub-workflow.d.ts +9 -0
- package/esm/src/workflow/dsl/sub-workflow.d.ts.map +1 -0
- package/esm/src/workflow/dsl/sub-workflow.js +19 -0
- package/esm/src/workflow/dsl/validation.d.ts +3 -0
- package/esm/src/workflow/dsl/validation.d.ts.map +1 -0
- package/esm/src/workflow/dsl/validation.js +6 -0
- package/esm/src/workflow/dsl/wait.d.ts +22 -0
- package/esm/src/workflow/dsl/wait.d.ts.map +1 -0
- package/esm/src/workflow/dsl/wait.js +53 -0
- package/esm/src/workflow/dsl/workflow.d.ts +29 -0
- package/esm/src/workflow/dsl/workflow.d.ts.map +1 -0
- package/esm/src/workflow/dsl/workflow.js +70 -0
- package/esm/src/workflow/executor/checkpoint-manager.d.ts +25 -0
- package/esm/src/workflow/executor/checkpoint-manager.d.ts.map +1 -0
- package/esm/src/workflow/executor/checkpoint-manager.js +113 -0
- package/esm/src/workflow/executor/dag/graph.d.ts +41 -0
- package/esm/src/workflow/executor/dag/graph.d.ts.map +1 -0
- package/esm/src/workflow/executor/dag/graph.js +110 -0
- package/esm/src/workflow/executor/dag/index.d.ts +25 -0
- package/esm/src/workflow/executor/dag/index.d.ts.map +1 -0
- package/esm/src/workflow/executor/dag/index.js +504 -0
- package/esm/src/workflow/executor/dag/types.d.ts +34 -0
- package/esm/src/workflow/executor/dag/types.d.ts.map +1 -0
- package/esm/src/workflow/executor/dag/types.js +8 -0
- package/esm/src/workflow/executor/dag/utils.d.ts +5 -0
- package/esm/src/workflow/executor/dag/utils.d.ts.map +1 -0
- package/esm/src/workflow/executor/dag/utils.js +14 -0
- package/esm/src/workflow/executor/dag-executor.d.ts +9 -0
- package/esm/src/workflow/executor/dag-executor.d.ts.map +1 -0
- package/esm/src/workflow/executor/dag-executor.js +8 -0
- package/esm/src/workflow/executor/index.d.ts +9 -0
- package/esm/src/workflow/executor/index.d.ts.map +1 -0
- package/esm/src/workflow/executor/index.js +4 -0
- package/esm/src/workflow/executor/step-executor.d.ts +116 -0
- package/esm/src/workflow/executor/step-executor.d.ts.map +1 -0
- package/esm/src/workflow/executor/step-executor.js +240 -0
- package/esm/src/workflow/executor/workflow-executor.d.ts +145 -0
- package/esm/src/workflow/executor/workflow-executor.d.ts.map +1 -0
- package/esm/src/workflow/executor/workflow-executor.js +385 -0
- package/esm/src/workflow/index.d.ts +76 -0
- package/esm/src/workflow/index.d.ts.map +1 -0
- package/esm/src/workflow/index.js +91 -0
- package/esm/src/workflow/react/index.d.ts +47 -0
- package/esm/src/workflow/react/index.d.ts.map +1 -0
- package/esm/src/workflow/react/index.js +42 -0
- package/esm/src/workflow/react/use-approval.d.ts +25 -0
- package/esm/src/workflow/react/use-approval.d.ts.map +1 -0
- package/esm/src/workflow/react/use-approval.js +91 -0
- package/esm/src/workflow/react/use-workflow-list.d.ts +27 -0
- package/esm/src/workflow/react/use-workflow-list.d.ts.map +1 -0
- package/esm/src/workflow/react/use-workflow-list.js +120 -0
- package/esm/src/workflow/react/use-workflow-start.d.ts +15 -0
- package/esm/src/workflow/react/use-workflow-start.d.ts.map +1 -0
- package/esm/src/workflow/react/use-workflow-start.js +43 -0
- package/esm/src/workflow/react/use-workflow.d.ts +26 -0
- package/esm/src/workflow/react/use-workflow.d.ts.map +1 -0
- package/esm/src/workflow/react/use-workflow.js +131 -0
- package/esm/src/workflow/registry.d.ts +113 -0
- package/esm/src/workflow/registry.d.ts.map +1 -0
- package/esm/src/workflow/registry.js +245 -0
- package/esm/src/workflow/runtime/agent-registry.d.ts +38 -0
- package/esm/src/workflow/runtime/agent-registry.d.ts.map +1 -0
- package/esm/src/workflow/runtime/agent-registry.js +119 -0
- package/esm/src/workflow/runtime/approval-manager.d.ts +63 -0
- package/esm/src/workflow/runtime/approval-manager.d.ts.map +1 -0
- package/esm/src/workflow/runtime/approval-manager.js +211 -0
- package/esm/src/workflow/runtime/index.d.ts +6 -0
- package/esm/src/workflow/runtime/index.d.ts.map +1 -0
- package/esm/src/workflow/runtime/index.js +5 -0
- package/esm/src/workflow/types.d.ts +402 -0
- package/esm/src/workflow/types.d.ts.map +1 -0
- package/esm/src/workflow/types.js +75 -0
- package/package.json +197 -134
- package/scripts/postinstall.js +126 -0
- package/src/_dnt.polyfills.ts +536 -0
- package/src/_dnt.shims.ts +82 -0
- package/src/deno.js +356 -0
- package/src/deps/deno.land/std@0.220.0/assert/assert.ts +19 -0
- package/src/deps/deno.land/std@0.220.0/assert/assertion_error.ts +19 -0
- package/src/deps/deno.land/std@0.220.0/fs/_create_walk_entry.ts +47 -0
- package/src/deps/deno.land/std@0.220.0/fs/_get_file_info_type.ts +22 -0
- package/src/deps/deno.land/std@0.220.0/fs/_is_same_path.ts +20 -0
- package/src/deps/deno.land/std@0.220.0/fs/_is_subdir.ts +26 -0
- package/src/deps/deno.land/std@0.220.0/fs/_to_path_string.ts +14 -0
- package/src/deps/deno.land/std@0.220.0/fs/copy.ts +342 -0
- package/src/deps/deno.land/std@0.220.0/fs/empty_dir.ts +74 -0
- package/src/deps/deno.land/std@0.220.0/fs/ensure_dir.ts +102 -0
- package/src/deps/deno.land/std@0.220.0/fs/ensure_file.ts +82 -0
- package/src/deps/deno.land/std@0.220.0/fs/ensure_link.ts +48 -0
- package/src/deps/deno.land/std@0.220.0/fs/ensure_symlink.ts +115 -0
- package/src/deps/deno.land/std@0.220.0/fs/eol.ts +68 -0
- package/src/deps/deno.land/std@0.220.0/fs/exists.ts +205 -0
- package/src/deps/deno.land/std@0.220.0/fs/expand_glob.ts +336 -0
- package/src/deps/deno.land/std@0.220.0/fs/mod.ts +19 -0
- package/src/deps/deno.land/std@0.220.0/fs/move.ts +122 -0
- package/src/deps/deno.land/std@0.220.0/fs/walk.ts +260 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/assert_path.ts +10 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/basename.ts +53 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/common.ts +26 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/constants.ts +49 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/dirname.ts +9 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/format.ts +25 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/from_file_url.ts +10 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/glob_to_reg_exp.ts +283 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/normalize.ts +9 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/normalize_string.ts +74 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/relative.ts +10 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/strip_trailing_separators.ts +25 -0
- package/src/deps/deno.land/std@0.220.0/path/_common/to_file_url.ts +17 -0
- package/src/deps/deno.land/std@0.220.0/path/_interface.ts +30 -0
- package/src/deps/deno.land/std@0.220.0/path/_os.ts +33 -0
- package/src/deps/deno.land/std@0.220.0/path/basename.ts +19 -0
- package/src/deps/deno.land/std@0.220.0/path/common.ts +24 -0
- package/src/deps/deno.land/std@0.220.0/path/constants.ts +7 -0
- package/src/deps/deno.land/std@0.220.0/path/dirname.ts +14 -0
- package/src/deps/deno.land/std@0.220.0/path/extname.ts +14 -0
- package/src/deps/deno.land/std@0.220.0/path/format.ts +15 -0
- package/src/deps/deno.land/std@0.220.0/path/from_file_url.ts +26 -0
- package/src/deps/deno.land/std@0.220.0/path/glob_to_regexp.ts +80 -0
- package/src/deps/deno.land/std@0.220.0/path/is_absolute.ts +14 -0
- package/src/deps/deno.land/std@0.220.0/path/is_glob.ts +35 -0
- package/src/deps/deno.land/std@0.220.0/path/join.ts +14 -0
- package/src/deps/deno.land/std@0.220.0/path/join_globs.ts +19 -0
- package/src/deps/deno.land/std@0.220.0/path/mod.ts +65 -0
- package/src/deps/deno.land/std@0.220.0/path/normalize.ts +15 -0
- package/src/deps/deno.land/std@0.220.0/path/normalize_glob.ts +21 -0
- package/src/deps/deno.land/std@0.220.0/path/parse.ts +15 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/_util.ts +10 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/basename.ts +37 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/common.ts +24 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/constants.ts +6 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/dirname.ts +53 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/extname.ts +71 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/format.ts +14 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/from_file_url.ts +21 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/glob_to_regexp.ts +79 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/is_absolute.ts +14 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/is_glob.ts +4 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/join.ts +25 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/join_globs.ts +30 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/mod.ts +43 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/normalize.ts +30 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/normalize_glob.ts +27 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/parse.ts +102 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/relative.ts +91 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/resolve.ts +58 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/to_file_url.ts +27 -0
- package/src/deps/deno.land/std@0.220.0/path/posix/to_namespaced_path.ts +11 -0
- package/src/deps/deno.land/std@0.220.0/path/relative.ts +21 -0
- package/src/deps/deno.land/std@0.220.0/path/resolve.ts +16 -0
- package/src/deps/deno.land/std@0.220.0/path/to_file_url.ts +26 -0
- package/src/deps/deno.land/std@0.220.0/path/to_namespaced_path.ts +16 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/_util.ts +28 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/basename.ts +37 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/common.ts +24 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/constants.ts +6 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/dirname.ts +103 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/extname.ts +76 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/format.ts +14 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/from_file_url.ts +30 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/glob_to_regexp.ts +79 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/is_absolute.ts +29 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/is_glob.ts +4 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/join.ts +72 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/join_globs.ts +30 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/mod.ts +43 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/normalize.ts +127 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/normalize_glob.ts +27 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/parse.ts +165 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/relative.ts +118 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/resolve.ts +163 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/to_file_url.ts +35 -0
- package/src/deps/deno.land/std@0.220.0/path/windows/to_namespaced_path.ts +49 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_error.ts +20 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_loader/loader.ts +1803 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_loader/loader_state.ts +75 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_mark.ts +79 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_state.ts +11 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/binary.ts +128 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/bool.ts +39 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/float.ts +125 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/function.ts +41 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/int.ts +188 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/map.ts +14 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/merge.ts +15 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/mod.ts +21 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/nil.ts +45 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/omap.ts +46 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/pairs.ts +47 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/regexp.ts +44 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/seq.ts +14 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/set.ts +31 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/str.ts +12 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/timestamp.ts +96 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_type/undefined.ts +22 -0
- package/src/deps/deno.land/std@0.220.0/yaml/_utils.ts +80 -0
- package/src/deps/deno.land/std@0.220.0/yaml/parse.ts +57 -0
- package/src/deps/deno.land/std@0.220.0/yaml/schema/core.ts +14 -0
- package/src/deps/deno.land/std@0.220.0/yaml/schema/default.ts +17 -0
- package/src/deps/deno.land/std@0.220.0/yaml/schema/extended.ts +39 -0
- package/src/deps/deno.land/std@0.220.0/yaml/schema/failsafe.ts +14 -0
- package/src/deps/deno.land/std@0.220.0/yaml/schema/json.ts +16 -0
- package/src/deps/deno.land/std@0.220.0/yaml/schema/mod.ts +11 -0
- package/src/deps/deno.land/std@0.220.0/yaml/schema.ts +109 -0
- package/src/deps/deno.land/std@0.220.0/yaml/type.ts +53 -0
- package/src/src/agent/ai-defaults.ts +72 -0
- package/src/src/agent/composition/composition.ts +184 -0
- package/src/src/agent/composition/index.ts +14 -0
- package/src/src/agent/debug/index.ts +8 -0
- package/src/src/agent/debug/inspector.ts +219 -0
- package/src/src/agent/factory.ts +162 -0
- package/src/src/agent/index.ts +63 -0
- package/src/src/agent/memory/index.ts +18 -0
- package/src/src/agent/memory/memory-interface.ts +65 -0
- package/src/src/agent/memory/memory.ts +296 -0
- package/src/src/agent/memory/redis.ts +184 -0
- package/src/src/agent/middleware/cache/cache.ts +292 -0
- package/src/src/agent/middleware/cache/index.ts +7 -0
- package/src/src/agent/middleware/chain.ts +64 -0
- package/src/src/agent/middleware/cost-tracking/index.ts +1 -0
- package/src/src/agent/middleware/cost-tracking/tracker.ts +264 -0
- package/src/src/agent/middleware/index.ts +28 -0
- package/src/src/agent/middleware/rate-limit/index.ts +1 -0
- package/src/src/agent/middleware/rate-limit/limiter.ts +200 -0
- package/src/src/agent/middleware/security/index.ts +1 -0
- package/src/src/agent/middleware/security/validator.ts +291 -0
- package/src/src/agent/react/client.ts +13 -0
- package/src/src/agent/react/index.ts +37 -0
- package/src/src/agent/react/use-agent.ts +130 -0
- package/src/src/agent/react/use-chat/index.ts +22 -0
- package/src/src/agent/react/use-chat/streaming/handler.ts +365 -0
- package/src/src/agent/react/use-chat/streaming/index.ts +13 -0
- package/src/src/agent/react/use-chat/streaming/parts-builder.ts +80 -0
- package/src/src/agent/react/use-chat/streaming/types.ts +36 -0
- package/src/src/agent/react/use-chat/types.ts +107 -0
- package/src/src/agent/react/use-chat/use-chat.ts +229 -0
- package/src/src/agent/react/use-chat/utils.ts +16 -0
- package/src/src/agent/react/use-completion.ts +136 -0
- package/src/src/agent/react/use-streaming.ts +121 -0
- package/src/src/agent/react/use-voice-input.ts +252 -0
- package/src/src/agent/runtime/constants.ts +6 -0
- package/src/src/agent/runtime/index.ts +577 -0
- package/src/src/agent/runtime/input-utils.ts +48 -0
- package/src/src/agent/runtime/message-converter.ts +117 -0
- package/src/src/agent/runtime/sse-utils.ts +26 -0
- package/src/src/agent/runtime/stream-handler.ts +198 -0
- package/src/src/agent/runtime/tool-helpers.ts +107 -0
- package/src/src/agent/streaming/index.ts +5 -0
- package/src/src/agent/streaming/stream-events.ts +124 -0
- package/src/src/agent/testing/agent-tester.ts +250 -0
- package/src/src/agent/testing/index.ts +18 -0
- package/src/src/agent/types.ts +198 -0
- package/src/src/build/asset-pipeline/css-optimizer/critical-css.ts +54 -0
- package/src/src/build/asset-pipeline/css-optimizer/css-bundle-cache.ts +116 -0
- package/src/src/build/asset-pipeline/css-optimizer/index.ts +88 -0
- package/src/src/build/asset-pipeline/css-optimizer/optimizer-service.ts +213 -0
- package/src/src/build/asset-pipeline/css-optimizer/strategies/index.ts +3 -0
- package/src/src/build/asset-pipeline/css-optimizer/strategies/lightning-strategy.ts +72 -0
- package/src/src/build/asset-pipeline/css-optimizer/strategies/minification-strategy.ts +29 -0
- package/src/src/build/asset-pipeline/css-optimizer/strategies/purge-strategy.ts +95 -0
- package/src/src/build/asset-pipeline/css-optimizer/types/index.ts +86 -0
- package/src/src/build/asset-pipeline/css-optimizer/utils.ts +163 -0
- package/src/src/build/asset-pipeline/image-optimizer/constants.ts +18 -0
- package/src/src/build/asset-pipeline/image-optimizer/format-processor.ts +20 -0
- package/src/src/build/asset-pipeline/image-optimizer/image-finder.ts +36 -0
- package/src/src/build/asset-pipeline/image-optimizer/index.ts +27 -0
- package/src/src/build/asset-pipeline/image-optimizer/manifest-manager.ts +53 -0
- package/src/src/build/asset-pipeline/image-optimizer/optimizer-core.ts +193 -0
- package/src/src/build/asset-pipeline/image-optimizer/sharp-loader.ts +17 -0
- package/src/src/build/asset-pipeline/image-optimizer/types.ts +87 -0
- package/src/src/build/asset-pipeline/image-optimizer/variant-generator.ts +113 -0
- package/src/src/build/asset-pipeline/index.ts +216 -0
- package/src/src/build/asset-pipeline/tailwind-processor/batch-processor.ts +67 -0
- package/src/src/build/asset-pipeline/tailwind-processor/css-utils.ts +12 -0
- package/src/src/build/asset-pipeline/tailwind-processor/detector.ts +50 -0
- package/src/src/build/asset-pipeline/tailwind-processor/index.ts +11 -0
- package/src/src/build/asset-pipeline/tailwind-processor/lightning-processor.ts +58 -0
- package/src/src/build/asset-pipeline/tailwind-processor/processor.ts +92 -0
- package/src/src/build/asset-pipeline/tailwind-processor/types.ts +26 -0
- package/src/src/build/bundler/code-splitter/build-context.ts +96 -0
- package/src/src/build/bundler/code-splitter/entry-points.ts +31 -0
- package/src/src/build/bundler/code-splitter/esbuild-plugin.ts +35 -0
- package/src/src/build/bundler/code-splitter/index.ts +73 -0
- package/src/src/build/bundler/code-splitter/manifest-builder.ts +142 -0
- package/src/src/build/bundler/code-splitter/splitter.ts +99 -0
- package/src/src/build/bundler/code-splitter/types.ts +46 -0
- package/src/src/build/bundler/index.ts +6 -0
- package/src/src/build/compiler/index.ts +22 -0
- package/src/src/build/compiler/mdx-compiler/code-generator.ts +18 -0
- package/src/src/build/compiler/mdx-compiler/compiler.ts +52 -0
- package/src/src/build/compiler/mdx-compiler/directory-compiler.ts +38 -0
- package/src/src/build/compiler/mdx-compiler/file-writer.ts +19 -0
- package/src/src/build/compiler/mdx-compiler/frontmatter-parser.ts +91 -0
- package/src/src/build/compiler/mdx-compiler/import-transformer.ts +27 -0
- package/src/src/build/compiler/mdx-compiler/index.ts +4 -0
- package/src/src/build/compiler/mdx-compiler/mdx-processor.ts +45 -0
- package/src/src/build/compiler/mdx-compiler/transpiler.ts +15 -0
- package/src/src/build/compiler/mdx-compiler/types.ts +21 -0
- package/src/src/build/compiler/mdx-compiler/validator.ts +60 -0
- package/src/src/build/compiler/mdx-compiler/watcher.ts +56 -0
- package/src/src/build/compiler/mdx-to-js.ts +246 -0
- package/src/src/build/config/environment.ts +62 -0
- package/src/src/build/embedded/preset.ts +318 -0
- package/src/src/build/index.ts +12 -0
- package/src/src/build/production-build/asset-generation.ts +189 -0
- package/src/src/build/production-build/build/build-cleanup.ts +38 -0
- package/src/src/build/production-build/build/build-executor.ts +61 -0
- package/src/src/build/production-build/build/build-initializer.ts +58 -0
- package/src/src/build/production-build/build/build-orchestrator.ts +155 -0
- package/src/src/build/production-build/build/build-setup.ts +48 -0
- package/src/src/build/production-build/build/code-splitter-orchestrator.ts +55 -0
- package/src/src/build/production-build/build/index.ts +58 -0
- package/src/src/build/production-build/build/output-generator.ts +140 -0
- package/src/src/build/production-build/build/route-collector.ts +44 -0
- package/src/src/build/production-build/client-runtime.ts +360 -0
- package/src/src/build/production-build/index.ts +26 -0
- package/src/src/build/production-build/manifest.ts +126 -0
- package/src/src/build/production-build/static-generation.ts +248 -0
- package/src/src/build/production-build/templates.ts +92 -0
- package/src/src/build/renderer/index.ts +9 -0
- package/src/src/build/renderer/services/css-bundler.ts +64 -0
- package/src/src/build/renderer/services/mdx-bundler.ts +246 -0
- package/src/src/build/renderer/services/optimizer.ts +45 -0
- package/src/src/build/renderer/services/script-bundler.ts +190 -0
- package/src/src/build/renderer/types/bundler-types.ts +12 -0
- package/src/src/build/renderer/utils/import-utils.ts +91 -0
- package/src/src/build/renderer/utils/loader-utils.ts +46 -0
- package/src/src/build/renderer/utils/plugin-utils.ts +6 -0
- package/src/src/build/utils/asset-utils.ts +142 -0
- package/src/src/build/utils/file-types.ts +204 -0
- package/src/src/build/vendor-bundle.ts +152 -0
- package/src/src/build/vendor-cache.ts +184 -0
- package/src/src/cache/backend.ts +614 -0
- package/src/src/cache/cache-key-builder.ts +130 -0
- package/src/src/cache/distributed-cache-init.ts +111 -0
- package/src/src/cache/index.ts +64 -0
- package/src/src/cache/keys.ts +410 -0
- package/src/src/cache/registry.ts +547 -0
- package/src/src/cache/request-cache-batcher.ts +142 -0
- package/src/src/cli/auth/browser.ts +27 -0
- package/src/src/cli/auth/callback-server.ts +283 -0
- package/src/src/cli/auth/constants.ts +13 -0
- package/src/src/cli/auth/index.ts +17 -0
- package/src/src/cli/auth/login.ts +279 -0
- package/src/src/cli/auth/token-store.ts +58 -0
- package/src/src/cli/commands/analyze-chunks.ts +89 -0
- package/src/src/cli/commands/build/config-display.ts +48 -0
- package/src/src/cli/commands/build/error-handler.ts +27 -0
- package/src/src/cli/commands/build/index.ts +46 -0
- package/src/src/cli/commands/build/stats-display.ts +39 -0
- package/src/src/cli/commands/build/types.ts +1 -0
- package/src/src/cli/commands/build.ts +2 -0
- package/src/src/cli/commands/clean.ts +158 -0
- package/src/src/cli/commands/demo/demo.ts +615 -0
- package/src/src/cli/commands/demo/index.ts +8 -0
- package/src/src/cli/commands/demo/steps.ts +68 -0
- package/src/src/cli/commands/deploy.ts +217 -0
- package/src/src/cli/commands/dev.ts +324 -0
- package/src/src/cli/commands/doctor/ai-checks.ts +65 -0
- package/src/src/cli/commands/doctor/index.ts +85 -0
- package/src/src/cli/commands/doctor/project-structure.ts +56 -0
- package/src/src/cli/commands/doctor/server-checks.ts +140 -0
- package/src/src/cli/commands/doctor/types.ts +6 -0
- package/src/src/cli/commands/doctor/version-checks.ts +61 -0
- package/src/src/cli/commands/generate/integration-generator.ts +640 -0
- package/src/src/cli/commands/generate.ts +185 -0
- package/src/src/cli/commands/init/config-generator.ts +33 -0
- package/src/src/cli/commands/init/index.ts +8 -0
- package/src/src/cli/commands/init/init-command.ts +418 -0
- package/src/src/cli/commands/init/interactive-wizard.ts +202 -0
- package/src/src/cli/commands/init/types.ts +17 -0
- package/src/src/cli/commands/install/detect.ts +51 -0
- package/src/src/cli/commands/install/index.ts +29 -0
- package/src/src/cli/commands/install/install.ts +230 -0
- package/src/src/cli/commands/install/registry.ts +70 -0
- package/src/src/cli/commands/install/types.ts +48 -0
- package/src/src/cli/commands/install/uninstall.ts +269 -0
- package/src/src/cli/commands/issues.ts +387 -0
- package/src/src/cli/commands/lock.ts +213 -0
- package/src/src/cli/commands/main.ts +271 -0
- package/src/src/cli/commands/merge.ts +223 -0
- package/src/src/cli/commands/new/fast-scaffold.ts +188 -0
- package/src/src/cli/commands/new/reserve-slug.ts +117 -0
- package/src/src/cli/commands/new-tui.ts +250 -0
- package/src/src/cli/commands/new.ts +244 -0
- package/src/src/cli/commands/pull.ts +455 -0
- package/src/src/cli/commands/push.ts +372 -0
- package/src/src/cli/commands/routes.ts +90 -0
- package/src/src/cli/commands/studio.ts +93 -0
- package/src/src/cli/commands/up.ts +218 -0
- package/src/src/cli/discovery/config-validator.ts +50 -0
- package/src/src/cli/discovery/index.ts +705 -0
- package/src/src/cli/help/command-definitions.ts +728 -0
- package/src/src/cli/help/command-help.ts +52 -0
- package/src/src/cli/help/formatters.ts +97 -0
- package/src/src/cli/help/index.ts +7 -0
- package/src/src/cli/help/logo.ts +5 -0
- package/src/src/cli/help/main-help.ts +52 -0
- package/src/src/cli/help/tips.ts +40 -0
- package/src/src/cli/help/types.ts +16 -0
- package/src/src/cli/index/arg-parser.ts +101 -0
- package/src/src/cli/index/build-handler.ts +58 -0
- package/src/src/cli/index/cli-main.ts +13 -0
- package/src/src/cli/index/command-router.ts +511 -0
- package/src/src/cli/index/dev-handler.ts +42 -0
- package/src/src/cli/index/generate-handler.ts +31 -0
- package/src/src/cli/index/index.ts +18 -0
- package/src/src/cli/index/studio-handler.ts +23 -0
- package/src/src/cli/index/types.ts +45 -0
- package/src/src/cli/index.ts +1 -0
- package/src/src/cli/main.ts +12 -0
- package/src/src/cli/mcp/advanced-tools.ts +1998 -0
- package/src/src/cli/mcp/error-collector.ts +249 -0
- package/src/src/cli/mcp/log-buffer.ts +214 -0
- package/src/src/cli/mcp/remote-file-tools.ts +749 -0
- package/src/src/cli/mcp/server.ts +587 -0
- package/src/src/cli/mcp/tools.ts +222 -0
- package/src/src/cli/shared/args.ts +130 -0
- package/src/src/cli/shared/config.ts +178 -0
- package/src/src/cli/sync/ignore.ts +144 -0
- package/src/src/cli/sync/index.ts +21 -0
- package/src/src/cli/sync/project-discovery.ts +112 -0
- package/src/src/cli/templates/feature-loader.ts +206 -0
- package/src/src/cli/templates/index.ts +106 -0
- package/src/src/cli/templates/integration-loader.ts +736 -0
- package/src/src/cli/templates/loader.ts +64 -0
- package/src/src/cli/templates/manifest.js +712 -0
- package/src/src/cli/templates/types.ts +174 -0
- package/src/src/cli/ui/animated-text.ts +89 -0
- package/src/src/cli/ui/ansi.ts +64 -0
- package/src/src/cli/ui/box.ts +266 -0
- package/src/src/cli/ui/colors.ts +159 -0
- package/src/src/cli/ui/components/banner.ts +164 -0
- package/src/src/cli/ui/components/index.ts +3 -0
- package/src/src/cli/ui/components/shortcuts.ts +30 -0
- package/src/src/cli/ui/components/table.ts +186 -0
- package/src/src/cli/ui/constants.ts +17 -0
- package/src/src/cli/ui/dot-matrix.ts +376 -0
- package/src/src/cli/ui/index.ts +8 -0
- package/src/src/cli/ui/keyboard.ts +148 -0
- package/src/src/cli/ui/layout.ts +158 -0
- package/src/src/cli/ui/progress.ts +224 -0
- package/src/src/cli/ui/tui.ts +296 -0
- package/src/src/cli/utils/env-prompt.ts +191 -0
- package/src/src/cli/utils/index.ts +298 -0
- package/src/src/cli/utils/package-manager.ts +140 -0
- package/src/src/cli/utils/terminal-select.ts +281 -0
- package/src/src/config/defaults.ts +75 -0
- package/src/src/config/define-config.ts +70 -0
- package/src/src/config/env.ts +160 -0
- package/src/src/config/index.ts +68 -0
- package/src/src/config/loader.ts +418 -0
- package/src/src/config/network-defaults.ts +58 -0
- package/src/src/config/runtime-config.ts +203 -0
- package/src/src/config/runtime-env.ts +208 -0
- package/src/src/config/schema.ts +356 -0
- package/src/src/config/types.ts +327 -0
- package/src/src/data/data-fetcher.ts +71 -0
- package/src/src/data/data-fetching-cache.ts +60 -0
- package/src/src/data/helpers.ts +9 -0
- package/src/src/data/index.ts +12 -0
- package/src/src/data/server-data-fetcher.ts +66 -0
- package/src/src/data/static-data-fetcher.ts +212 -0
- package/src/src/data/static-paths-fetcher.ts +33 -0
- package/src/src/data/types.ts +41 -0
- package/src/src/embeddings/base.ts +108 -0
- package/src/src/embeddings/index.ts +67 -0
- package/src/src/embeddings/providers/cohere.ts +55 -0
- package/src/src/embeddings/providers/openai.ts +62 -0
- package/src/src/embeddings/providers/voyageai.ts +57 -0
- package/src/src/embeddings/types.ts +37 -0
- package/src/src/errors/agent-errors.ts +43 -0
- package/src/src/errors/build-errors.ts +15 -0
- package/src/src/errors/catalog/build-errors.ts +88 -0
- package/src/src/errors/catalog/config-errors.ts +106 -0
- package/src/src/errors/catalog/deployment-errors.ts +42 -0
- package/src/src/errors/catalog/dev-errors.ts +41 -0
- package/src/src/errors/catalog/factory.ts +23 -0
- package/src/src/errors/catalog/general-errors.ts +61 -0
- package/src/src/errors/catalog/index.ts +58 -0
- package/src/src/errors/catalog/module-errors.ts +90 -0
- package/src/src/errors/catalog/route-errors.ts +74 -0
- package/src/src/errors/catalog/rsc-errors.ts +86 -0
- package/src/src/errors/catalog/runtime-errors.ts +97 -0
- package/src/src/errors/catalog/server-errors.ts +74 -0
- package/src/src/errors/catalog/types.ts +15 -0
- package/src/src/errors/error-codes.ts +112 -0
- package/src/src/errors/error-context.ts +169 -0
- package/src/src/errors/error-handlers.ts +128 -0
- package/src/src/errors/index.ts +74 -0
- package/src/src/errors/runtime-errors.ts +15 -0
- package/src/src/errors/system-errors.ts +38 -0
- package/src/src/errors/types.ts +29 -0
- package/src/src/errors/user-friendly/error-catalog.ts +129 -0
- package/src/src/errors/user-friendly/error-formatter.ts +114 -0
- package/src/src/errors/user-friendly/error-identifier.ts +40 -0
- package/src/src/errors/user-friendly/error-wrapper.ts +24 -0
- package/src/src/errors/user-friendly/index.ts +4 -0
- package/src/src/errors/veryfront-error.ts +133 -0
- package/src/src/html/dev-scripts.ts +73 -0
- package/src/src/html/html-detection.ts +8 -0
- package/src/src/html/html-escape.ts +17 -0
- package/src/src/html/html-injection.ts +106 -0
- package/src/src/html/html-shell-generator.ts +370 -0
- package/src/src/html/hydration-script-builder/dev-client-renderer.ts +19 -0
- package/src/src/html/hydration-script-builder/dev-component-manifest.ts +14 -0
- package/src/src/html/hydration-script-builder/dev-error-logger.ts +58 -0
- package/src/src/html/hydration-script-builder/dev-scripts.ts +46 -0
- package/src/src/html/hydration-script-builder/hydration-data-generator.ts +61 -0
- package/src/src/html/hydration-script-builder/index.ts +11 -0
- package/src/src/html/hydration-script-builder/prod-hydration.ts +36 -0
- package/src/src/html/hydration-script-builder/prod-scripts.ts +23 -0
- package/src/src/html/hydration-script-builder/templates/index.ts +4 -0
- package/src/src/html/hydration-script-builder/templates/loader.ts +98 -0
- package/src/src/html/hydration-script-builder/templates/renderer.ts +174 -0
- package/src/src/html/hydration-script-builder/templates/router.ts +756 -0
- package/src/src/html/hydration-script-builder/templates/spa-renderer.ts +104 -0
- package/src/src/html/hydration-script-builder/types.ts +26 -0
- package/src/src/html/index.ts +34 -0
- package/src/src/html/metadata-builder.ts +40 -0
- package/src/src/html/metadata-extraction.ts +55 -0
- package/src/src/html/styles-builder/css-pregeneration.ts +140 -0
- package/src/src/html/styles-builder/dev-styles.ts +71 -0
- package/src/src/html/styles-builder/index.ts +21 -0
- package/src/src/html/styles-builder/production-styles.ts +83 -0
- package/src/src/html/styles-builder/tailwind-compiler.ts +750 -0
- package/src/src/html/styles-builder/theme-variables.ts +78 -0
- package/src/src/html/tag-generators.ts +116 -0
- package/src/src/html/types.ts +57 -0
- package/src/src/html/utils.ts +281 -0
- package/src/src/index.ts +61 -0
- package/src/src/issues/core.ts +388 -0
- package/src/src/issues/index.ts +39 -0
- package/src/src/issues/mcp.ts +223 -0
- package/src/src/issues/schema.ts +156 -0
- package/src/src/issues/types.ts +54 -0
- package/src/src/mcp/index.ts +22 -0
- package/src/src/mcp/registry.ts +41 -0
- package/src/src/mcp/server.ts +334 -0
- package/src/src/mcp/types.ts +29 -0
- package/src/src/middleware/builtin/index.ts +20 -0
- package/src/src/middleware/builtin/logger.ts +206 -0
- package/src/src/middleware/builtin/security/rate-limit.ts +99 -0
- package/src/src/middleware/builtin/security/redis-rate-limit.ts +95 -0
- package/src/src/middleware/builtin/security/types.ts +34 -0
- package/src/src/middleware/builtin/timeout.ts +95 -0
- package/src/src/middleware/builtin/types.ts +37 -0
- package/src/src/middleware/core/context.ts +63 -0
- package/src/src/middleware/core/index.ts +10 -0
- package/src/src/middleware/core/pipeline/composer.ts +39 -0
- package/src/src/middleware/core/pipeline/executor.ts +64 -0
- package/src/src/middleware/core/pipeline/index.ts +4 -0
- package/src/src/middleware/core/pipeline/pipeline.ts +64 -0
- package/src/src/middleware/core/pipeline/types.ts +1 -0
- package/src/src/middleware/core/types.ts +28 -0
- package/src/src/middleware/index.ts +11 -0
- package/src/src/modules/component-registry/index.ts +2 -0
- package/src/src/modules/component-registry/registry.ts +254 -0
- package/src/src/modules/import-map/default-import-map.ts +77 -0
- package/src/src/modules/import-map/index.ts +8 -0
- package/src/src/modules/import-map/loader.ts +124 -0
- package/src/src/modules/import-map/merger.ts +19 -0
- package/src/src/modules/import-map/preloader.ts +81 -0
- package/src/src/modules/import-map/resolver.ts +101 -0
- package/src/src/modules/import-map/transformer.ts +50 -0
- package/src/src/modules/import-map/types.ts +8 -0
- package/src/src/modules/manifest/route-module-manifest.ts +266 -0
- package/src/src/modules/react-loader/component-loader.ts +76 -0
- package/src/src/modules/react-loader/extract-component.ts +23 -0
- package/src/src/modules/react-loader/index.ts +8 -0
- package/src/src/modules/react-loader/path-resolver.ts +30 -0
- package/src/src/modules/react-loader/ssr-module-loader/cache/index.ts +22 -0
- package/src/src/modules/react-loader/ssr-module-loader/cache/memory.ts +137 -0
- package/src/src/modules/react-loader/ssr-module-loader/cache/redis.ts +99 -0
- package/src/src/modules/react-loader/ssr-module-loader/concurrency/index.ts +9 -0
- package/src/src/modules/react-loader/ssr-module-loader/concurrency/semaphore.ts +56 -0
- package/src/src/modules/react-loader/ssr-module-loader/constants.ts +39 -0
- package/src/src/modules/react-loader/ssr-module-loader/index.ts +44 -0
- package/src/src/modules/react-loader/ssr-module-loader/loader.ts +869 -0
- package/src/src/modules/react-loader/ssr-module-loader/types.ts +36 -0
- package/src/src/modules/react-loader/temp-directory.ts +58 -0
- package/src/src/modules/react-loader/types.ts +20 -0
- package/src/src/modules/react-loader/unified-loader.ts +112 -0
- package/src/src/modules/server/api-server.ts +57 -0
- package/src/src/modules/server/index.ts +12 -0
- package/src/src/modules/server/module-batch-handler.ts +405 -0
- package/src/src/modules/server/module-server.ts +579 -0
- package/src/src/modules/server/rate-limiter.ts +27 -0
- package/src/src/modules/server/ssr-import-rewriter.ts +113 -0
- package/src/src/modules/server/websocket-handler.ts +130 -0
- package/src/src/oauth/handlers/callback-handler.ts +151 -0
- package/src/src/oauth/handlers/index.ts +20 -0
- package/src/src/oauth/handlers/init-handler.ts +119 -0
- package/src/src/oauth/index.ts +89 -0
- package/src/src/oauth/providers/atlassian.ts +67 -0
- package/src/src/oauth/providers/base.ts +331 -0
- package/src/src/oauth/providers/common.ts +382 -0
- package/src/src/oauth/providers/google.ts +69 -0
- package/src/src/oauth/providers/index.ts +61 -0
- package/src/src/oauth/providers/microsoft.ts +85 -0
- package/src/src/oauth/token-store/index.ts +10 -0
- package/src/src/oauth/token-store/memory.ts +72 -0
- package/src/src/oauth/types.ts +74 -0
- package/src/src/observability/auto-instrument/configurator.ts +12 -0
- package/src/src/observability/auto-instrument/http-instrumentation.ts +215 -0
- package/src/src/observability/auto-instrument/index.ts +17 -0
- package/src/src/observability/auto-instrument/orchestrator.ts +58 -0
- package/src/src/observability/auto-instrument/react-instrumentation.ts +83 -0
- package/src/src/observability/auto-instrument/types.ts +48 -0
- package/src/src/observability/auto-instrument/wrappers.ts +127 -0
- package/src/src/observability/index.ts +65 -0
- package/src/src/observability/instruments/build-instruments.ts +44 -0
- package/src/src/observability/instruments/cache-instruments.ts +62 -0
- package/src/src/observability/instruments/data-instruments.ts +36 -0
- package/src/src/observability/instruments/http-instruments.ts +27 -0
- package/src/src/observability/instruments/index.ts +8 -0
- package/src/src/observability/instruments/instruments-factory.ts +67 -0
- package/src/src/observability/instruments/memory-instruments.ts +70 -0
- package/src/src/observability/instruments/render-instruments.ts +30 -0
- package/src/src/observability/instruments/rsc-instruments.ts +61 -0
- package/src/src/observability/metrics/config.ts +98 -0
- package/src/src/observability/metrics/index.ts +134 -0
- package/src/src/observability/metrics/manager.ts +123 -0
- package/src/src/observability/metrics/recorder.ts +136 -0
- package/src/src/observability/metrics/types.ts +73 -0
- package/src/src/observability/simple-metrics/index.ts +74 -0
- package/src/src/observability/simple-metrics/metrics-recorder.ts +259 -0
- package/src/src/observability/simple-metrics/metrics-state.ts +97 -0
- package/src/src/observability/simple-metrics/observability-loader.ts +52 -0
- package/src/src/observability/simple-metrics/otel-instruments.ts +87 -0
- package/src/src/observability/simple-metrics/types.ts +69 -0
- package/src/src/observability/tracing/config.ts +75 -0
- package/src/src/observability/tracing/context-propagation.ts +87 -0
- package/src/src/observability/tracing/index.ts +129 -0
- package/src/src/observability/tracing/manager.ts +105 -0
- package/src/src/observability/tracing/otlp-setup.ts +360 -0
- package/src/src/observability/tracing/span-names.ts +109 -0
- package/src/src/observability/tracing/span-operations.ts +97 -0
- package/src/src/observability/tracing/types.ts +47 -0
- package/src/src/platform/adapters/base.ts +188 -0
- package/src/src/platform/adapters/bun.ts +20 -0
- package/src/src/platform/adapters/deno.ts +1 -0
- package/src/src/platform/adapters/detect.ts +84 -0
- package/src/src/platform/adapters/fallback-wrapper.ts +156 -0
- package/src/src/platform/adapters/fs/cache/file-cache.ts +430 -0
- package/src/src/platform/adapters/fs/cache/size-estimator.ts +6 -0
- package/src/src/platform/adapters/fs/cache/types.ts +20 -0
- package/src/src/platform/adapters/fs/factory.ts +104 -0
- package/src/src/platform/adapters/fs/github/adapter.ts +157 -0
- package/src/src/platform/adapters/fs/github/directory-operations.ts +94 -0
- package/src/src/platform/adapters/fs/github/github-api-client.ts +223 -0
- package/src/src/platform/adapters/fs/github/index.ts +28 -0
- package/src/src/platform/adapters/fs/github/read-operations.ts +187 -0
- package/src/src/platform/adapters/fs/github/schemas.ts +80 -0
- package/src/src/platform/adapters/fs/github/stat-operations.ts +274 -0
- package/src/src/platform/adapters/fs/github/types.ts +111 -0
- package/src/src/platform/adapters/fs/index.ts +28 -0
- package/src/src/platform/adapters/fs/integration.ts +97 -0
- package/src/src/platform/adapters/fs/shared-types.ts +12 -0
- package/src/src/platform/adapters/fs/veryfront/adapter.ts +1372 -0
- package/src/src/platform/adapters/fs/veryfront/cache-keys.ts +42 -0
- package/src/src/platform/adapters/fs/veryfront/directory-operations.ts +232 -0
- package/src/src/platform/adapters/fs/veryfront/index.ts +2 -0
- package/src/src/platform/adapters/fs/veryfront/invalidation-state.ts +174 -0
- package/src/src/platform/adapters/fs/veryfront/multi-project-adapter.ts +273 -0
- package/src/src/platform/adapters/fs/veryfront/path-normalizer.ts +38 -0
- package/src/src/platform/adapters/fs/veryfront/proxy-manager.ts +494 -0
- package/src/src/platform/adapters/fs/veryfront/read-operations.ts +540 -0
- package/src/src/platform/adapters/fs/veryfront/stat-operations.ts +456 -0
- package/src/src/platform/adapters/fs/veryfront/types.ts +200 -0
- package/src/src/platform/adapters/fs/wrapper.ts +257 -0
- package/src/src/platform/adapters/index.ts +86 -0
- package/src/src/platform/adapters/mock.ts +181 -0
- package/src/src/platform/adapters/node.ts +18 -0
- package/src/src/platform/adapters/redis/deno.ts +125 -0
- package/src/src/platform/adapters/redis/index.ts +24 -0
- package/src/src/platform/adapters/redis/interface.ts +42 -0
- package/src/src/platform/adapters/redis/modules.ts +49 -0
- package/src/src/platform/adapters/redis/node.ts +138 -0
- package/src/src/platform/adapters/redis/types.ts +84 -0
- package/src/src/platform/adapters/redis/utils.ts +27 -0
- package/src/src/platform/adapters/registry.ts +188 -0
- package/src/src/platform/adapters/runtime/bun/adapter.ts +49 -0
- package/src/src/platform/adapters/runtime/bun/environment-adapter.ts +20 -0
- package/src/src/platform/adapters/runtime/bun/filesystem-adapter.ts +186 -0
- package/src/src/platform/adapters/runtime/bun/http-server.ts +46 -0
- package/src/src/platform/adapters/runtime/bun/index.ts +14 -0
- package/src/src/platform/adapters/runtime/bun/types.ts +46 -0
- package/src/src/platform/adapters/runtime/bun/websocket-adapter.ts +51 -0
- package/src/src/platform/adapters/runtime/deno/adapter.ts +428 -0
- package/src/src/platform/adapters/runtime/deno/index.ts +1 -0
- package/src/src/platform/adapters/runtime/node/adapter.ts +49 -0
- package/src/src/platform/adapters/runtime/node/environment-adapter.ts +22 -0
- package/src/src/platform/adapters/runtime/node/filesystem-adapter.ts +145 -0
- package/src/src/platform/adapters/runtime/node/http-server.ts +167 -0
- package/src/src/platform/adapters/runtime/node/index.ts +12 -0
- package/src/src/platform/adapters/runtime/node/types.ts +42 -0
- package/src/src/platform/adapters/runtime/node/websocket-adapter.ts +165 -0
- package/src/src/platform/adapters/runtime/shared/node-based-shell-adapter.ts +32 -0
- package/src/src/platform/adapters/runtime/shared/shared-watcher.ts +54 -0
- package/src/src/platform/adapters/runtime/shared/watcher-queue.ts +65 -0
- package/src/src/platform/adapters/runtime-detection.ts +10 -0
- package/src/src/platform/adapters/security/index.ts +2 -0
- package/src/src/platform/adapters/token/factory.ts +48 -0
- package/src/src/platform/adapters/token/index.ts +18 -0
- package/src/src/platform/adapters/token/integration.ts +71 -0
- package/src/src/platform/adapters/token/veryfront/adapter.ts +75 -0
- package/src/src/platform/adapters/token/veryfront/api-client.ts +220 -0
- package/src/src/platform/adapters/token/veryfront/index.ts +10 -0
- package/src/src/platform/adapters/token/veryfront/memory-adapter.ts +69 -0
- package/src/src/platform/adapters/token/veryfront/types.ts +138 -0
- package/src/src/platform/adapters/veryfront-api-client/client.ts +487 -0
- package/src/src/platform/adapters/veryfront-api-client/index.ts +35 -0
- package/src/src/platform/adapters/veryfront-api-client/operations.ts +452 -0
- package/src/src/platform/adapters/veryfront-api-client/retry-handler.ts +121 -0
- package/src/src/platform/adapters/veryfront-api-client/schemas.ts +224 -0
- package/src/src/platform/adapters/veryfront-api-client/types.ts +42 -0
- package/src/src/platform/compat/console/deno.ts +56 -0
- package/src/src/platform/compat/console/index.ts +89 -0
- package/src/src/platform/compat/console/node.ts +70 -0
- package/src/src/platform/compat/console/types.ts +18 -0
- package/src/src/platform/compat/crypto.ts +29 -0
- package/src/src/platform/compat/flags.ts +55 -0
- package/src/src/platform/compat/fs.ts +320 -0
- package/src/src/platform/compat/http/deno-server.ts +23 -0
- package/src/src/platform/compat/http/factory.ts +8 -0
- package/src/src/platform/compat/http/index.ts +21 -0
- package/src/src/platform/compat/http/node-server.ts +97 -0
- package/src/src/platform/compat/http/node-types.ts +34 -0
- package/src/src/platform/compat/http/request-adapter.ts +15 -0
- package/src/src/platform/compat/http/responses.ts +216 -0
- package/src/src/platform/compat/http/types.ts +24 -0
- package/src/src/platform/compat/http/websocket.ts +33 -0
- package/src/src/platform/compat/index.ts +9 -0
- package/src/src/platform/compat/kv/factory.ts +49 -0
- package/src/src/platform/compat/kv/index.ts +4 -0
- package/src/src/platform/compat/kv/memory-adapter.ts +71 -0
- package/src/src/platform/compat/kv/sqlite-adapter.ts +114 -0
- package/src/src/platform/compat/kv/types.ts +31 -0
- package/src/src/platform/compat/media-types.ts +23 -0
- package/src/src/platform/compat/path/basic-operations.ts +54 -0
- package/src/src/platform/compat/path/index.ts +9 -0
- package/src/src/platform/compat/path/parse-format.ts +37 -0
- package/src/src/platform/compat/path/resolution.ts +91 -0
- package/src/src/platform/compat/path/runtime.ts +24 -0
- package/src/src/platform/compat/path/security.ts +28 -0
- package/src/src/platform/compat/path/types.ts +22 -0
- package/src/src/platform/compat/path/url-conversion.ts +53 -0
- package/src/src/platform/compat/path-helper.ts +11 -0
- package/src/src/platform/compat/process.ts +574 -0
- package/src/src/platform/compat/react-paths.ts +103 -0
- package/src/src/platform/compat/runtime.ts +49 -0
- package/src/src/platform/compat/shims/std-path.ts +41 -0
- package/src/src/platform/compat/std/front-matter-yaml.ts +35 -0
- package/src/src/platform/compat/std/fs.ts +170 -0
- package/src/src/platform/compat/stdin.ts +216 -0
- package/src/src/platform/core-platform.ts +176 -0
- package/src/src/platform/index.ts +3 -0
- package/src/src/prompt/factory.ts +41 -0
- package/src/src/prompt/index.ts +4 -0
- package/src/src/prompt/registry.ts +67 -0
- package/src/src/prompt/types.ts +12 -0
- package/src/src/provider/adapters/ai-sdk.ts +70 -0
- package/src/src/provider/adapters/index.ts +2 -0
- package/src/src/provider/anthropic.ts +375 -0
- package/src/src/provider/base.ts +303 -0
- package/src/src/provider/factory.ts +186 -0
- package/src/src/provider/google.ts +124 -0
- package/src/src/provider/index.ts +22 -0
- package/src/src/provider/openai.ts +172 -0
- package/src/src/provider/types.ts +74 -0
- package/src/src/react/compat/config-generator.ts +162 -0
- package/src/src/react/compat/hooks-adapter.ts +220 -0
- package/src/src/react/compat/index.ts +44 -0
- package/src/src/react/compat/ssr-adapter/html-wrapper.ts +40 -0
- package/src/src/react/compat/ssr-adapter/index.ts +7 -0
- package/src/src/react/compat/ssr-adapter/response-builder.ts +45 -0
- package/src/src/react/compat/ssr-adapter/server-loader.ts +54 -0
- package/src/src/react/compat/ssr-adapter/stream-renderer.ts +234 -0
- package/src/src/react/compat/ssr-adapter/string-renderer.ts +74 -0
- package/src/src/react/compat/ssr-adapter/types.ts +37 -0
- package/src/src/react/compat/version-detector/compatibility-checker.ts +56 -0
- package/src/src/react/compat/version-detector/feature-detector.ts +91 -0
- package/src/src/react/compat/version-detector/index.ts +21 -0
- package/src/src/react/compat/version-detector/types.ts +42 -0
- package/src/src/react/compat/version-detector/version-cache.ts +32 -0
- package/src/src/react/compat/version-detector/version-parser.ts +37 -0
- package/src/src/react/components/AppWrapper.tsx +45 -0
- package/src/src/react/components/Head.tsx +108 -0
- package/src/src/react/components/LayoutComponent.tsx +63 -0
- package/src/src/react/components/Link.tsx +17 -0
- package/src/src/react/components/MDXProvider.tsx +22 -0
- package/src/src/react/components/ProviderComponent.tsx +58 -0
- package/src/src/react/components/ai/agent-card.tsx +145 -0
- package/src/src/react/components/ai/chat/components/animations.tsx +33 -0
- package/src/src/react/components/ai/chat/components/empty-state.tsx +114 -0
- package/src/src/react/components/ai/chat/components/message-actions.tsx +66 -0
- package/src/src/react/components/ai/chat/components/reasoning.tsx +47 -0
- package/src/src/react/components/ai/chat/components/tool-ui.tsx +213 -0
- package/src/src/react/components/ai/chat/composition/api.tsx +67 -0
- package/src/src/react/components/ai/chat/index.tsx +304 -0
- package/src/src/react/components/ai/chat/utils/message-parts.ts +83 -0
- package/src/src/react/components/ai/chat.tsx +29 -0
- package/src/src/react/components/ai/error-boundary.tsx +109 -0
- package/src/src/react/components/ai/icons/index.ts +172 -0
- package/src/src/react/components/ai/index.ts +37 -0
- package/src/src/react/components/ai/markdown.tsx +274 -0
- package/src/src/react/components/ai/message.tsx +170 -0
- package/src/src/react/components/ai/theme.ts +126 -0
- package/src/src/react/components/index.ts +13 -0
- package/src/src/react/components/optimized-image/OptimizedBackgroundImage.tsx +40 -0
- package/src/src/react/components/optimized-image/OptimizedImage.tsx +81 -0
- package/src/src/react/components/optimized-image/SimpleOptimizedImage.tsx +42 -0
- package/src/src/react/components/optimized-image/helpers.ts +25 -0
- package/src/src/react/components/optimized-image/index.ts +6 -0
- package/src/src/react/components/optimized-image/useOptimizedImage.ts +29 -0
- package/src/src/react/components/optimized-image/utils.tsx +50 -0
- package/src/src/react/context/index.ts +54 -0
- package/src/src/react/fonts/index.ts +90 -0
- package/src/src/react/head-collector.ts +89 -0
- package/src/src/react/index.ts +13 -0
- package/src/src/react/primitives/agent-primitives.tsx +83 -0
- package/src/src/react/primitives/chat-container.tsx +17 -0
- package/src/src/react/primitives/index.ts +64 -0
- package/src/src/react/primitives/input-box.tsx +197 -0
- package/src/src/react/primitives/message-list.tsx +83 -0
- package/src/src/react/primitives/tool-primitives.tsx +127 -0
- package/src/src/react/router/index.ts +55 -0
- package/src/src/rendering/app-reserved.ts +110 -0
- package/src/src/rendering/app-route-resolver.ts +175 -0
- package/src/src/rendering/cache/cache-coordinator.ts +103 -0
- package/src/src/rendering/cache/index.ts +9 -0
- package/src/src/rendering/cache/stores/api-store.ts +213 -0
- package/src/src/rendering/cache/stores/filesystem-store.ts +110 -0
- package/src/src/rendering/cache/stores/index.ts +5 -0
- package/src/src/rendering/cache/stores/kv-store.ts +101 -0
- package/src/src/rendering/cache/stores/memory-store.ts +73 -0
- package/src/src/rendering/cache/stores/redis-store.ts +256 -0
- package/src/src/rendering/cache/types.ts +17 -0
- package/src/src/rendering/chunk-optimizer.ts +275 -0
- package/src/src/rendering/client/browser-logger.ts +66 -0
- package/src/src/rendering/client/index.ts +3 -0
- package/src/src/rendering/client/prefetch/link-observer.ts +153 -0
- package/src/src/rendering/client/prefetch/network-utils.ts +49 -0
- package/src/src/rendering/client/prefetch/prefetch-queue.ts +172 -0
- package/src/src/rendering/client/prefetch/resource-hints.ts +154 -0
- package/src/src/rendering/client/prefetch.ts +167 -0
- package/src/src/rendering/client/router.ts +265 -0
- package/src/src/rendering/client/state-bridge.ts +192 -0
- package/src/src/rendering/component-handling.ts +171 -0
- package/src/src/rendering/context/render-context.ts +135 -0
- package/src/src/rendering/element-validator/element-inspector.ts +186 -0
- package/src/src/rendering/element-validator/element-normalizer.ts +71 -0
- package/src/src/rendering/element-validator/index.ts +16 -0
- package/src/src/rendering/element-validator/primitive-checks.ts +108 -0
- package/src/src/rendering/element-validator/types.ts +15 -0
- package/src/src/rendering/element-validator/validator-core.ts +47 -0
- package/src/src/rendering/factories/service-factories.ts +112 -0
- package/src/src/rendering/index.ts +18 -0
- package/src/src/rendering/layouts/index.ts +20 -0
- package/src/src/rendering/layouts/layout-applicator.ts +386 -0
- package/src/src/rendering/layouts/layout-collector.ts +352 -0
- package/src/src/rendering/layouts/layout-compiler.ts +71 -0
- package/src/src/rendering/layouts/types.ts +16 -0
- package/src/src/rendering/layouts/utils/app-resolver.ts +68 -0
- package/src/src/rendering/layouts/utils/applicator.ts +226 -0
- package/src/src/rendering/layouts/utils/compiler.ts +30 -0
- package/src/src/rendering/layouts/utils/component-loader.ts +263 -0
- package/src/src/rendering/layouts/utils/discovery.ts +170 -0
- package/src/src/rendering/layouts/utils/ensure-valid-child.ts +49 -0
- package/src/src/rendering/layouts/utils/hash-calculator.ts +45 -0
- package/src/src/rendering/orchestrator/compiler-service.ts +38 -0
- package/src/src/rendering/orchestrator/config.ts +120 -0
- package/src/src/rendering/orchestrator/file-resolver/candidates.ts +34 -0
- package/src/src/rendering/orchestrator/file-resolver/index.ts +69 -0
- package/src/src/rendering/orchestrator/html.ts +354 -0
- package/src/src/rendering/orchestrator/layout.ts +251 -0
- package/src/src/rendering/orchestrator/lifecycle.ts +276 -0
- package/src/src/rendering/orchestrator/mdx.ts +81 -0
- package/src/src/rendering/orchestrator/module-loader/cache.ts +23 -0
- package/src/src/rendering/orchestrator/module-loader/esm-rewriter.ts +92 -0
- package/src/src/rendering/orchestrator/module-loader/index.ts +306 -0
- package/src/src/rendering/orchestrator/pipeline.ts +732 -0
- package/src/src/rendering/orchestrator/ssr-orchestrator.ts +147 -0
- package/src/src/rendering/orchestrator/ssr.ts +229 -0
- package/src/src/rendering/orchestrator/types.ts +92 -0
- package/src/src/rendering/page-renderer.ts +260 -0
- package/src/src/rendering/page-rendering.ts +152 -0
- package/src/src/rendering/page-resolution/index.ts +15 -0
- package/src/src/rendering/page-resolution/page-resolver.ts +135 -0
- package/src/src/rendering/renderer.ts +506 -0
- package/src/src/rendering/route-params-extractor.ts +156 -0
- package/src/src/rendering/router-detection.ts +210 -0
- package/src/src/rendering/rsc/component-analyzer.ts +160 -0
- package/src/src/rendering/rsc/export-extractor.ts +42 -0
- package/src/src/rendering/rsc/production-optimizer.ts +135 -0
- package/src/src/rendering/rsc/server-action-guard.ts +7 -0
- package/src/src/rendering/rsc/server-renderer/component-detector.ts +44 -0
- package/src/src/rendering/rsc/server-renderer/html-generator.ts +45 -0
- package/src/src/rendering/rsc/server-renderer/index.ts +12 -0
- package/src/src/rendering/rsc/server-renderer/prop-serializer.ts +66 -0
- package/src/src/rendering/rsc/server-renderer/rsc-renderer.ts +44 -0
- package/src/src/rendering/rsc/server-renderer/tree-processor.ts +113 -0
- package/src/src/rendering/rsc/types.ts +9 -0
- package/src/src/rendering/script-page-handling.ts +403 -0
- package/src/src/rendering/shared/context-aware-cache.ts +232 -0
- package/src/src/rendering/shared/shared-services.ts +114 -0
- package/src/src/rendering/snippet-renderer.ts +368 -0
- package/src/src/rendering/ssr/component-registry.ts +428 -0
- package/src/src/rendering/ssr-globals/context.ts +56 -0
- package/src/src/rendering/ssr-globals/dom-stubs.ts +423 -0
- package/src/src/rendering/ssr-globals/fetch-interceptor.ts +135 -0
- package/src/src/rendering/ssr-globals/index.ts +82 -0
- package/src/src/rendering/ssr-globals.ts +14 -0
- package/src/src/rendering/ssr-renderer.ts +248 -0
- package/src/src/rendering/utils/index.ts +12 -0
- package/src/src/rendering/utils/react-helpers.ts +31 -0
- package/src/src/rendering/utils/stream-utils.ts +126 -0
- package/src/src/rendering/virtual-module-system.ts +118 -0
- package/src/src/resource/factory.ts +59 -0
- package/src/src/resource/index.ts +4 -0
- package/src/src/resource/registry.ts +65 -0
- package/src/src/resource/types.ts +73 -0
- package/src/src/routing/api/api-route-matcher.ts +181 -0
- package/src/src/routing/api/context-builder.ts +67 -0
- package/src/src/routing/api/error-handler.ts +35 -0
- package/src/src/routing/api/handler.ts +302 -0
- package/src/src/routing/api/index.ts +20 -0
- package/src/src/routing/api/method-validator.ts +17 -0
- package/src/src/routing/api/module-loader/esbuild-plugin.ts +204 -0
- package/src/src/routing/api/module-loader/http-validator.ts +40 -0
- package/src/src/routing/api/module-loader/loader.ts +517 -0
- package/src/src/routing/api/module-loader/security-config.ts +20 -0
- package/src/src/routing/api/module-loader/types.ts +32 -0
- package/src/src/routing/api/openapi/path-utils.ts +164 -0
- package/src/src/routing/api/openapi/spec-generator.ts +287 -0
- package/src/src/routing/api/openapi/types.ts +141 -0
- package/src/src/routing/api/route-discovery.ts +76 -0
- package/src/src/routing/api/route-executor.ts +126 -0
- package/src/src/routing/client/dom-utils.ts +173 -0
- package/src/src/routing/client/index.ts +27 -0
- package/src/src/routing/client/navigation-handlers.ts +127 -0
- package/src/src/routing/client/page-loader.ts +214 -0
- package/src/src/routing/client/page-transition.ts +96 -0
- package/src/src/routing/client/types.ts +39 -0
- package/src/src/routing/client/viewport-prefetch.ts +80 -0
- package/src/src/routing/index.ts +47 -0
- package/src/src/routing/matchers/index.ts +5 -0
- package/src/src/routing/matchers/pattern-route-matcher.ts +41 -0
- package/src/src/routing/matchers/route-matcher.ts +41 -0
- package/src/src/routing/matchers/route-parser.ts +70 -0
- package/src/src/routing/matchers/types.ts +13 -0
- package/src/src/routing/registry/index.ts +1 -0
- package/src/src/routing/registry/registry.ts +156 -0
- package/src/src/routing/registry/types.ts +6 -0
- package/src/src/routing/slug-mapper/dynamic-route-matcher.ts +64 -0
- package/src/src/routing/slug-mapper/index.ts +5 -0
- package/src/src/routing/slug-mapper/path-candidate-generator.ts +62 -0
- package/src/src/routing/slug-mapper/slug-normalizer.ts +26 -0
- package/src/src/routing/slug-mapper/types.ts +8 -0
- package/src/src/security/client/html-sanitizer.ts +137 -0
- package/src/src/security/http/auth.ts +126 -0
- package/src/src/security/http/base-handler.ts +175 -0
- package/src/src/security/http/client-hints.ts +49 -0
- package/src/src/security/http/config.ts +98 -0
- package/src/src/security/http/cors/constants.ts +17 -0
- package/src/src/security/http/cors/headers.ts +85 -0
- package/src/src/security/http/cors/index.ts +30 -0
- package/src/src/security/http/cors/middleware.ts +43 -0
- package/src/src/security/http/cors/preflight.ts +85 -0
- package/src/src/security/http/cors/types.ts +31 -0
- package/src/src/security/http/cors/validators.ts +197 -0
- package/src/src/security/http/handlers-index.ts +4 -0
- package/src/src/security/http/middleware/config-loader.ts +53 -0
- package/src/src/security/http/middleware/content-types.ts +28 -0
- package/src/src/security/http/middleware/cors-handler.ts +20 -0
- package/src/src/security/http/middleware/etag.ts +11 -0
- package/src/src/security/http/middleware/index.ts +13 -0
- package/src/src/security/http/middleware/types.ts +37 -0
- package/src/src/security/http/response/builder.ts +60 -0
- package/src/src/security/http/response/cache-handler.ts +29 -0
- package/src/src/security/http/response/constants.ts +14 -0
- package/src/src/security/http/response/fluent-methods.ts +136 -0
- package/src/src/security/http/response/index.ts +14 -0
- package/src/src/security/http/response/response-methods.ts +89 -0
- package/src/src/security/http/response/security-handler.ts +137 -0
- package/src/src/security/http/response/static-helpers.ts +167 -0
- package/src/src/security/http/response/types.ts +60 -0
- package/src/src/security/index.ts +80 -0
- package/src/src/security/input-validation/errors.ts +9 -0
- package/src/src/security/input-validation/handler.ts +51 -0
- package/src/src/security/input-validation/index.ts +12 -0
- package/src/src/security/input-validation/limits.ts +95 -0
- package/src/src/security/input-validation/parsers.ts +105 -0
- package/src/src/security/input-validation/sanitizers.ts +44 -0
- package/src/src/security/input-validation/schemas.ts +34 -0
- package/src/src/security/input-validation/types.ts +34 -0
- package/src/src/security/path-validation/canonical.ts +76 -0
- package/src/src/security/path-validation/index.ts +156 -0
- package/src/src/security/path-validation/normalization.ts +66 -0
- package/src/src/security/path-validation/presets.ts +61 -0
- package/src/src/security/path-validation/rules.ts +68 -0
- package/src/src/security/path-validation/types.ts +38 -0
- package/src/src/security/path-validation.ts +19 -0
- package/src/src/security/sandbox/constants.ts +8 -0
- package/src/src/security/sandbox/deno-sandbox.ts +107 -0
- package/src/src/security/sandbox/permission-system.ts +104 -0
- package/src/src/security/secure-fs.ts +319 -0
- package/src/src/server/bootstrap.ts +154 -0
- package/src/src/server/build-app-route-renderer.ts +248 -0
- package/src/src/server/build-routes.ts +135 -0
- package/src/src/server/build-service-worker.ts +185 -0
- package/src/src/server/build-types.ts +48 -0
- package/src/src/server/context/cache-invalidation.ts +129 -0
- package/src/src/server/context/enriched-context.ts +138 -0
- package/src/src/server/context/request-context.ts +67 -0
- package/src/src/server/dev-server/error-overlay/error-formatter.ts +56 -0
- package/src/src/server/dev-server/error-overlay/html-template.ts +306 -0
- package/src/src/server/dev-server/error-overlay/index.ts +19 -0
- package/src/src/server/dev-server/error-overlay/overlay-renderer.ts +11 -0
- package/src/src/server/dev-server/error-overlay/stack-parser.ts +25 -0
- package/src/src/server/dev-server/file-watch-setup.ts +154 -0
- package/src/src/server/dev-server/file-watcher.ts +105 -0
- package/src/src/server/dev-server/hmr/index.ts +14 -0
- package/src/src/server/dev-server/hmr/message-handler.ts +13 -0
- package/src/src/server/dev-server/hmr/runtime-generator.ts +20 -0
- package/src/src/server/dev-server/hmr/templates.ts +155 -0
- package/src/src/server/dev-server/hmr-server.ts +209 -0
- package/src/src/server/dev-server/hmr-types.ts +29 -0
- package/src/src/server/dev-server/index.ts +12 -0
- package/src/src/server/dev-server/middleware.ts +230 -0
- package/src/src/server/dev-server/request-handler.ts +178 -0
- package/src/src/server/dev-server/route-discovery.ts +246 -0
- package/src/src/server/dev-server/server.ts +278 -0
- package/src/src/server/dev-server/types.ts +30 -0
- package/src/src/server/dev-server.ts +8 -0
- package/src/src/server/handlers/dev/dashboard/api.ts +563 -0
- package/src/src/server/handlers/dev/dashboard/html-shell.ts +43 -0
- package/src/src/server/handlers/dev/dashboard/index.ts +59 -0
- package/src/src/server/handlers/dev/dashboard/ui-handler.ts +118 -0
- package/src/src/server/handlers/dev/debug-context.ts +108 -0
- package/src/src/server/handlers/dev/endpoints.ts +632 -0
- package/src/src/server/handlers/dev/files/dev-file-handler.ts +65 -0
- package/src/src/server/handlers/dev/files/esbuild-bundler.ts +38 -0
- package/src/src/server/handlers/dev/files/esbuild-plugins.ts +204 -0
- package/src/src/server/handlers/dev/files/index.ts +12 -0
- package/src/src/server/handlers/dev/files/path-validator.ts +57 -0
- package/src/src/server/handlers/dev/projects/api.ts +38 -0
- package/src/src/server/handlers/dev/projects/html-shell.ts +40 -0
- package/src/src/server/handlers/dev/projects/index.ts +75 -0
- package/src/src/server/handlers/dev/projects/ui-handler.ts +118 -0
- package/src/src/server/handlers/dev/styles-css-handler.ts +211 -0
- package/src/src/server/handlers/monitoring/client-log.ts +102 -0
- package/src/src/server/handlers/monitoring/health.ts +96 -0
- package/src/src/server/handlers/monitoring/memory.ts +167 -0
- package/src/src/server/handlers/monitoring/metrics.ts +67 -0
- package/src/src/server/handlers/preview/hmr-handler.ts +308 -0
- package/src/src/server/handlers/preview/markdown-preview-handler.ts +291 -0
- package/src/src/server/handlers/request/api/api-handler-wrapper.ts +192 -0
- package/src/src/server/handlers/request/api/app-router-handler.ts +75 -0
- package/src/src/server/handlers/request/api/app-router-resolver.ts +87 -0
- package/src/src/server/handlers/request/api/index.ts +6 -0
- package/src/src/server/handlers/request/api/pages-api-handler.ts +59 -0
- package/src/src/server/handlers/request/api/security-headers.ts +41 -0
- package/src/src/server/handlers/request/api/types.ts +14 -0
- package/src/src/server/handlers/request/css-handler.ts +80 -0
- package/src/src/server/handlers/request/lib-modules-handler.ts +130 -0
- package/src/src/server/handlers/request/module/batch-module-handler.ts +36 -0
- package/src/src/server/handlers/request/module/data-endpoint-handler.ts +70 -0
- package/src/src/server/handlers/request/module/index.ts +8 -0
- package/src/src/server/handlers/request/module/module-handler.ts +125 -0
- package/src/src/server/handlers/request/module/module-server-handler.ts +57 -0
- package/src/src/server/handlers/request/module/page-data-endpoint-handler.ts +95 -0
- package/src/src/server/handlers/request/module/page-module-handler.ts +79 -0
- package/src/src/server/handlers/request/module/virtual-module-handler.ts +53 -0
- package/src/src/server/handlers/request/openapi-docs-handler.ts +88 -0
- package/src/src/server/handlers/request/openapi-handler.ts +134 -0
- package/src/src/server/handlers/request/rsc/endpoints/action-handler.ts +62 -0
- package/src/src/server/handlers/request/rsc/endpoints/action-parser.ts +50 -0
- package/src/src/server/handlers/request/rsc/endpoints/endpoint-router.ts +274 -0
- package/src/src/server/handlers/request/rsc/endpoints/handler-registry.ts +54 -0
- package/src/src/server/handlers/request/rsc/endpoints/index.ts +6 -0
- package/src/src/server/handlers/request/rsc/endpoints/script-handlers.ts +103 -0
- package/src/src/server/handlers/request/rsc/endpoints/types.ts +28 -0
- package/src/src/server/handlers/request/rsc/handlers/component-resolver.ts +78 -0
- package/src/src/server/handlers/request/rsc/handlers/handler.ts +65 -0
- package/src/src/server/handlers/request/rsc/handlers/hydrator-handler.ts +106 -0
- package/src/src/server/handlers/request/rsc/handlers/index.ts +10 -0
- package/src/src/server/handlers/request/rsc/handlers/manifest-handler.ts +45 -0
- package/src/src/server/handlers/request/rsc/handlers/page-handler.ts +56 -0
- package/src/src/server/handlers/request/rsc/handlers/render-handler.ts +146 -0
- package/src/src/server/handlers/request/rsc/handlers/stream-handler.ts +101 -0
- package/src/src/server/handlers/request/rsc/handlers/types.ts +36 -0
- package/src/src/server/handlers/request/rsc/index.ts +79 -0
- package/src/src/server/handlers/request/snippet-handler.ts +134 -0
- package/src/src/server/handlers/request/ssr/error-page-fallback.ts +234 -0
- package/src/src/server/handlers/request/ssr/etag-handler.ts +19 -0
- package/src/src/server/handlers/request/ssr/index.ts +4 -0
- package/src/src/server/handlers/request/ssr/not-found-fallback.ts +101 -0
- package/src/src/server/handlers/request/ssr/ssr-handler.ts +476 -0
- package/src/src/server/handlers/request/static.ts +338 -0
- package/src/src/server/handlers/response/base.ts +1 -0
- package/src/src/server/handlers/response/cors.ts +147 -0
- package/src/src/server/handlers/response/not-found.ts +155 -0
- package/src/src/server/handlers/studio/endpoints.ts +47 -0
- package/src/src/server/handlers/types.ts +21 -0
- package/src/src/server/handlers/utils/content-types.ts +107 -0
- package/src/src/server/handlers/utils/etag.ts +45 -0
- package/src/src/server/index.ts +35 -0
- package/src/src/server/production-server.ts +252 -0
- package/src/src/server/reload-notifier.ts +167 -0
- package/src/src/server/shared/renderer/adapter.ts +289 -0
- package/src/src/server/shared/renderer/index.ts +2 -0
- package/src/src/server/shared/renderer/memory/pressure.ts +55 -0
- package/src/src/server/shared/renderer-factory.ts +14 -0
- package/src/src/server/universal-handler/index.ts +1035 -0
- package/src/src/server/universal-handler/project-isolation.ts +209 -0
- package/src/src/server/universal-handler/proxy-environment.ts +16 -0
- package/src/src/server/universal-handler/request-tracker.ts +275 -0
- package/src/src/server/utils/domain-lookup.ts +211 -0
- package/src/src/server/utils/domain-parser.ts +168 -0
- package/src/src/server/utils/error-html.ts +156 -0
- package/src/src/studio/bridge-template.ts +739 -0
- package/src/src/studio/element-selector-injector.ts +108 -0
- package/src/src/studio/hash-utils.ts +14 -0
- package/src/src/tool/executor.ts +22 -0
- package/src/src/tool/factory.ts +199 -0
- package/src/src/tool/index.ts +19 -0
- package/src/src/tool/registry.ts +67 -0
- package/src/src/tool/schema/index.ts +3 -0
- package/src/src/tool/schema/json-schema.ts +15 -0
- package/src/src/tool/schema/zod-json-schema.ts +146 -0
- package/src/src/tool/testing/index.ts +3 -0
- package/src/src/tool/testing/tool-tester.ts +209 -0
- package/src/src/tool/types.ts +126 -0
- package/src/src/transforms/esm/http-bundler.ts +209 -0
- package/src/src/transforms/esm/http-cache.ts +574 -0
- package/src/src/transforms/esm/import-parser.ts +251 -0
- package/src/src/transforms/esm/import-rewriter.ts +185 -0
- package/src/src/transforms/esm/index.ts +22 -0
- package/src/src/transforms/esm/lexer.ts +174 -0
- package/src/src/transforms/esm/package-registry.ts +131 -0
- package/src/src/transforms/esm/path-resolver.ts +279 -0
- package/src/src/transforms/esm/react-imports.ts +55 -0
- package/src/src/transforms/esm/transform-cache.ts +169 -0
- package/src/src/transforms/esm/transform-utils.ts +34 -0
- package/src/src/transforms/esm/types.ts +20 -0
- package/src/src/transforms/esm-transform.ts +14 -0
- package/src/src/transforms/md/compiler/index.ts +1 -0
- package/src/src/transforms/md/compiler/md-compiler.ts +144 -0
- package/src/src/transforms/md/utils.ts +35 -0
- package/src/src/transforms/mdx/compiler/frontmatter-extractor.ts +78 -0
- package/src/src/transforms/mdx/compiler/import-rewriter.ts +136 -0
- package/src/src/transforms/mdx/compiler/index.ts +38 -0
- package/src/src/transforms/mdx/compiler/mdx-compiler.ts +104 -0
- package/src/src/transforms/mdx/compiler/types.ts +12 -0
- package/src/src/transforms/mdx/esm-module-loader/cache/index.ts +152 -0
- package/src/src/transforms/mdx/esm-module-loader/constants.ts +32 -0
- package/src/src/transforms/mdx/esm-module-loader/index.ts +9 -0
- package/src/src/transforms/mdx/esm-module-loader/loader.ts +617 -0
- package/src/src/transforms/mdx/esm-module-loader/module-fetcher/index.ts +737 -0
- package/src/src/transforms/mdx/esm-module-loader/resolution/file-finder.ts +165 -0
- package/src/src/transforms/mdx/esm-module-loader/types.ts +59 -0
- package/src/src/transforms/mdx/esm-module-loader/utils/hash.ts +12 -0
- package/src/src/transforms/mdx/esm-module-loader/utils/stub-module.ts +83 -0
- package/src/src/transforms/mdx/index.ts +125 -0
- package/src/src/transforms/mdx/mdx-cache-adapter.ts +182 -0
- package/src/src/transforms/mdx/module-loader/metadata-extractor.ts +108 -0
- package/src/src/transforms/mdx/module-loader/string-parser.ts +58 -0
- package/src/src/transforms/mdx/module-loader/types.ts +49 -0
- package/src/src/transforms/mdx/parser.ts +66 -0
- package/src/src/transforms/mdx/types.ts +83 -0
- package/src/src/transforms/pipeline/context.ts +149 -0
- package/src/src/transforms/pipeline/index.ts +178 -0
- package/src/src/transforms/pipeline/stages/compile.ts +71 -0
- package/src/src/transforms/pipeline/stages/finalize.ts +18 -0
- package/src/src/transforms/pipeline/stages/index.ts +10 -0
- package/src/src/transforms/pipeline/stages/parse.ts +31 -0
- package/src/src/transforms/pipeline/stages/resolve-aliases.ts +26 -0
- package/src/src/transforms/pipeline/stages/resolve-bare.ts +31 -0
- package/src/src/transforms/pipeline/stages/resolve-context.ts +11 -0
- package/src/src/transforms/pipeline/stages/resolve-react.ts +26 -0
- package/src/src/transforms/pipeline/stages/resolve-relative.ts +44 -0
- package/src/src/transforms/pipeline/stages/ssr-http-cache.ts +50 -0
- package/src/src/transforms/pipeline/stages/ssr-http-stub.ts +99 -0
- package/src/src/transforms/pipeline/types.ts +141 -0
- package/src/src/transforms/plugins/babel-node-positions.ts +196 -0
- package/src/src/transforms/plugins/plugin-loader.ts +31 -0
- package/src/src/transforms/plugins/remark-headings.ts +105 -0
- package/src/src/transforms/plugins/remark-mdx-utils.ts +250 -0
- package/src/src/types/app.ts +6 -0
- package/src/src/types/branded.ts +40 -0
- package/src/src/types/bundler.ts +51 -0
- package/src/src/types/entities/getEntityInfo.ts +399 -0
- package/src/src/types/entities.ts +162 -0
- package/src/src/types/global-guards.ts +21 -0
- package/src/src/types/hmr.ts +20 -0
- package/src/src/types/index.ts +223 -0
- package/src/src/types/rsc.ts +45 -0
- package/src/src/types/server.ts +156 -0
- package/src/src/utils/base64url.ts +15 -0
- package/src/src/utils/bundle-manifest-init.ts +130 -0
- package/src/src/utils/bundle-manifest-kv.ts +72 -0
- package/src/src/utils/bundle-manifest-redis.ts +73 -0
- package/src/src/utils/bundle-manifest.ts +168 -0
- package/src/src/utils/cache/eviction/eviction-manager.ts +153 -0
- package/src/src/utils/cache/stores/memory/entry-manager.ts +105 -0
- package/src/src/utils/cache/stores/memory/lru-cache-adapter.ts +203 -0
- package/src/src/utils/cache/stores/memory/lru-list-manager.ts +58 -0
- package/src/src/utils/cache/stores/memory/lru-node.ts +10 -0
- package/src/src/utils/cache/stores/memory/types.ts +32 -0
- package/src/src/utils/cache-dir.ts +32 -0
- package/src/src/utils/chunk-utils.ts +16 -0
- package/src/src/utils/circuit-breaker.ts +142 -0
- package/src/src/utils/constants/buffers.ts +31 -0
- package/src/src/utils/constants/build.ts +6 -0
- package/src/src/utils/constants/cache.ts +160 -0
- package/src/src/utils/constants/cdn.ts +70 -0
- package/src/src/utils/constants/crypto.ts +12 -0
- package/src/src/utils/constants/env.ts +28 -0
- package/src/src/utils/constants/hash.ts +2 -0
- package/src/src/utils/constants/hmr.ts +24 -0
- package/src/src/utils/constants/html.ts +9 -0
- package/src/src/utils/constants/http.ts +63 -0
- package/src/src/utils/constants/index.ts +17 -0
- package/src/src/utils/constants/limits.ts +28 -0
- package/src/src/utils/constants/metrics.ts +39 -0
- package/src/src/utils/constants/network.ts +31 -0
- package/src/src/utils/constants/priorities.ts +31 -0
- package/src/src/utils/constants/retry.ts +20 -0
- package/src/src/utils/constants/security.ts +11 -0
- package/src/src/utils/constants/server.ts +141 -0
- package/src/src/utils/cookie-utils.ts +28 -0
- package/src/src/utils/env-loader.ts +147 -0
- package/src/src/utils/feature-flags.ts +9 -0
- package/src/src/utils/file-discovery.ts +258 -0
- package/src/src/utils/format-utils.ts +97 -0
- package/src/src/utils/hash-utils.ts +40 -0
- package/src/src/utils/id.ts +37 -0
- package/src/src/utils/import-lockfile.ts +263 -0
- package/src/src/utils/index.ts +29 -0
- package/src/src/utils/logger/env.ts +33 -0
- package/src/src/utils/logger/index.ts +2 -0
- package/src/src/utils/logger/logger.ts +466 -0
- package/src/src/utils/lru-wrapper.ts +115 -0
- package/src/src/utils/memoize.ts +86 -0
- package/src/src/utils/memory/index.ts +17 -0
- package/src/src/utils/memory/profiler.ts +221 -0
- package/src/src/utils/parallel.ts +148 -0
- package/src/src/utils/path-utils.ts +83 -0
- package/src/src/utils/paths.ts +24 -0
- package/src/src/utils/perf-timer.ts +114 -0
- package/src/src/utils/platform.ts +20 -0
- package/src/src/utils/redis-client.ts +134 -0
- package/src/src/utils/route-path-utils.ts +212 -0
- package/src/src/utils/runtime-guards.ts +40 -0
- package/src/src/utils/semaphore.ts +106 -0
- package/src/src/utils/singleflight.ts +25 -0
- package/src/src/utils/version.ts +21 -0
- package/src/src/workflow/api/index.ts +6 -0
- package/src/src/workflow/api/workflow-client.ts +193 -0
- package/src/src/workflow/backends/cloudflare.ts +103 -0
- package/src/src/workflow/backends/inngest.ts +85 -0
- package/src/src/workflow/backends/memory.ts +396 -0
- package/src/src/workflow/backends/redis/index.ts +576 -0
- package/src/src/workflow/backends/redis/types.ts +58 -0
- package/src/src/workflow/backends/redis.ts +10 -0
- package/src/src/workflow/backends/temporal.ts +112 -0
- package/src/src/workflow/backends/types.ts +113 -0
- package/src/src/workflow/blob/gcs-storage.ts +293 -0
- package/src/src/workflow/blob/index.ts +8 -0
- package/src/src/workflow/blob/local-storage.ts +175 -0
- package/src/src/workflow/blob/s3-storage.ts +297 -0
- package/src/src/workflow/blob/types.ts +38 -0
- package/src/src/workflow/dsl/branch.ts +73 -0
- package/src/src/workflow/dsl/index.ts +23 -0
- package/src/src/workflow/dsl/loop.ts +119 -0
- package/src/src/workflow/dsl/map.ts +44 -0
- package/src/src/workflow/dsl/parallel.ts +52 -0
- package/src/src/workflow/dsl/step.ts +64 -0
- package/src/src/workflow/dsl/sub-workflow.ts +36 -0
- package/src/src/workflow/dsl/validation.ts +6 -0
- package/src/src/workflow/dsl/wait.ts +80 -0
- package/src/src/workflow/dsl/workflow.ts +123 -0
- package/src/src/workflow/executor/checkpoint-manager.ts +159 -0
- package/src/src/workflow/executor/dag/graph.ts +150 -0
- package/src/src/workflow/executor/dag/index.ts +691 -0
- package/src/src/workflow/executor/dag/types.ts +40 -0
- package/src/src/workflow/executor/dag/utils.ts +16 -0
- package/src/src/workflow/executor/dag-executor.ts +14 -0
- package/src/src/workflow/executor/index.ts +16 -0
- package/src/src/workflow/executor/step-executor.ts +368 -0
- package/src/src/workflow/executor/workflow-executor.ts +550 -0
- package/src/src/workflow/index.ts +222 -0
- package/src/src/workflow/react/index.ts +51 -0
- package/src/src/workflow/react/use-approval.ts +140 -0
- package/src/src/workflow/react/use-workflow-list.ts +171 -0
- package/src/src/workflow/react/use-workflow-start.ts +73 -0
- package/src/src/workflow/react/use-workflow.ts +190 -0
- package/src/src/workflow/registry.ts +346 -0
- package/src/src/workflow/runtime/agent-registry.ts +159 -0
- package/src/src/workflow/runtime/approval-manager.ts +319 -0
- package/src/src/workflow/runtime/index.ts +11 -0
- package/src/src/workflow/types.ts +577 -0
- package/dist/ai/client.d.ts +0 -2
- package/dist/ai/client.js +0 -28
- package/dist/ai/client.js.map +0 -7
- package/dist/ai/components.d.ts +0 -11
- package/dist/ai/components.js +0 -1769
- package/dist/ai/components.js.map +0 -7
- package/dist/ai/dev.d.ts +0 -7
- package/dist/ai/dev.js +0 -963
- package/dist/ai/dev.js.map +0 -7
- package/dist/ai/index.d.ts +0 -129
- package/dist/ai/index.js +0 -9114
- package/dist/ai/index.js.map +0 -7
- package/dist/ai/primitives.d.ts +0 -29
- package/dist/ai/primitives.js +0 -388
- package/dist/ai/primitives.js.map +0 -7
- package/dist/ai/production.d.ts +0 -13
- package/dist/ai/production.js +0 -1064
- package/dist/ai/production.js.map +0 -7
- package/dist/ai/react.d.ts +0 -104
- package/dist/ai/react.js +0 -899
- package/dist/ai/react.js.map +0 -7
- package/dist/ai/workflow-react.js +0 -479
- package/dist/ai/workflow-react.js.map +0 -7
- package/dist/ai/workflow.js +0 -5844
- package/dist/ai/workflow.js.map +0 -7
- package/dist/components.d.ts +0 -33
- package/dist/components.js +0 -9935
- package/dist/components.js.map +0 -7
- package/dist/config.d.ts +0 -30
- package/dist/config.js +0 -1388
- package/dist/config.js.map +0 -7
- package/dist/context.d.ts +0 -44
- package/dist/context.js +0 -52
- package/dist/context.js.map +0 -7
- package/dist/data.d.ts +0 -21
- package/dist/data.js +0 -1403
- package/dist/data.js.map +0 -7
- package/dist/fonts.d.ts +0 -24
- package/dist/fonts.js +0 -68
- package/dist/fonts.js.map +0 -7
- package/dist/head.d.ts +0 -21
- package/dist/head.js +0 -34
- package/dist/head.js.map +0 -7
- package/dist/index.d.ts +0 -11
- package/dist/index.js +0 -12254
- package/dist/index.js.map +0 -7
- package/dist/integrations/_base/connector.json +0 -19
- package/dist/integrations/_base/files/SETUP.md +0 -701
- package/dist/integrations/_base/files/app/api/integrations/status/route.ts +0 -38
- package/dist/integrations/_base/files/app/api/integrations/token-storage/route.ts +0 -30
- package/dist/integrations/_base/files/app/components/ServiceConnections.tsx +0 -164
- package/dist/integrations/_base/files/app/page.tsx +0 -128
- package/dist/integrations/_base/files/app/setup/page.tsx +0 -1333
- package/dist/integrations/_base/files/lib/oauth.ts +0 -145
- package/dist/integrations/_base/files/lib/token-store-examples.ts +0 -435
- package/dist/integrations/_base/files/lib/token-store.ts +0 -389
- package/dist/integrations/airtable/connector.json +0 -99
- package/dist/integrations/airtable/files/ai/tools/create-record.ts +0 -25
- package/dist/integrations/airtable/files/ai/tools/get-base.ts +0 -34
- package/dist/integrations/airtable/files/ai/tools/get-record.ts +0 -23
- package/dist/integrations/airtable/files/ai/tools/list-bases.ts +0 -19
- package/dist/integrations/airtable/files/ai/tools/list-records.ts +0 -47
- package/dist/integrations/airtable/files/app/api/auth/airtable/callback/route.ts +0 -31
- package/dist/integrations/airtable/files/app/api/auth/airtable/route.ts +0 -9
- package/dist/integrations/airtable/files/lib/airtable-client.ts +0 -244
- package/dist/integrations/anthropic/README.md +0 -181
- package/dist/integrations/anthropic/connector.json +0 -88
- package/dist/integrations/anthropic/files/_env.example +0 -4
- package/dist/integrations/anthropic/files/ai/tools/get-organization.ts +0 -36
- package/dist/integrations/anthropic/files/ai/tools/get-usage.ts +0 -100
- package/dist/integrations/anthropic/files/ai/tools/list-api-keys.ts +0 -64
- package/dist/integrations/anthropic/files/ai/tools/list-members.ts +0 -65
- package/dist/integrations/anthropic/files/ai/tools/list-workspaces.ts +0 -35
- package/dist/integrations/anthropic/files/lib/anthropic-admin-client.ts +0 -264
- package/dist/integrations/asana/connector.json +0 -85
- package/dist/integrations/asana/files/_env.example +0 -4
- package/dist/integrations/asana/files/ai/tools/create-task.ts +0 -34
- package/dist/integrations/asana/files/ai/tools/get-task.ts +0 -26
- package/dist/integrations/asana/files/ai/tools/list-projects.ts +0 -26
- package/dist/integrations/asana/files/ai/tools/list-tasks.ts +0 -50
- package/dist/integrations/asana/files/ai/tools/update-task.ts +0 -36
- package/dist/integrations/asana/files/app/api/auth/asana/callback/route.ts +0 -31
- package/dist/integrations/asana/files/app/api/auth/asana/route.ts +0 -7
- package/dist/integrations/asana/files/lib/asana-client.ts +0 -162
- package/dist/integrations/aws/connector.json +0 -72
- package/dist/integrations/aws/files/_env.example +0 -10
- package/dist/integrations/aws/files/ai/tools/get-s3-object.ts +0 -62
- package/dist/integrations/aws/files/ai/tools/list-ec2-instances.ts +0 -56
- package/dist/integrations/aws/files/ai/tools/list-lambda-functions.ts +0 -57
- package/dist/integrations/aws/files/ai/tools/list-s3-buckets.ts +0 -44
- package/dist/integrations/aws/files/ai/tools/list-s3-objects.ts +0 -58
- package/dist/integrations/aws/files/lib/aws-client.ts +0 -255
- package/dist/integrations/bitbucket/connector.json +0 -85
- package/dist/integrations/bitbucket/files/_env.example +0 -9
- package/dist/integrations/bitbucket/files/ai/tools/create-pull-request.ts +0 -83
- package/dist/integrations/bitbucket/files/ai/tools/list-issues.ts +0 -112
- package/dist/integrations/bitbucket/files/ai/tools/list-pull-requests.ts +0 -91
- package/dist/integrations/bitbucket/files/ai/tools/list-repositories.ts +0 -78
- package/dist/integrations/bitbucket/files/app/api/auth/bitbucket/callback/route.ts +0 -31
- package/dist/integrations/bitbucket/files/app/api/auth/bitbucket/route.ts +0 -9
- package/dist/integrations/bitbucket/files/lib/bitbucket-client.ts +0 -316
- package/dist/integrations/box/connector.json +0 -85
- package/dist/integrations/box/files/_env.example +0 -4
- package/dist/integrations/box/files/ai/tools/create-folder.ts +0 -28
- package/dist/integrations/box/files/ai/tools/get-file.ts +0 -29
- package/dist/integrations/box/files/ai/tools/list-files.ts +0 -33
- package/dist/integrations/box/files/ai/tools/search-files.ts +0 -35
- package/dist/integrations/box/files/ai/tools/upload-file.ts +0 -32
- package/dist/integrations/box/files/app/api/auth/box/callback/route.ts +0 -31
- package/dist/integrations/box/files/app/api/auth/box/route.ts +0 -7
- package/dist/integrations/box/files/lib/box-client.ts +0 -280
- package/dist/integrations/calendar/connector.json +0 -83
- package/dist/integrations/calendar/files/_env.example +0 -29
- package/dist/integrations/calendar/files/ai/tools/create-event.ts +0 -79
- package/dist/integrations/calendar/files/ai/tools/find-free-time.ts +0 -104
- package/dist/integrations/calendar/files/ai/tools/list-events.ts +0 -94
- package/dist/integrations/calendar/files/app/api/auth/calendar/callback/route.ts +0 -31
- package/dist/integrations/calendar/files/app/api/auth/calendar/route.ts +0 -9
- package/dist/integrations/calendar/files/lib/calendar-client.ts +0 -309
- package/dist/integrations/clickup/connector.json +0 -85
- package/dist/integrations/clickup/files/_env.example +0 -4
- package/dist/integrations/clickup/files/ai/tools/create-task.ts +0 -64
- package/dist/integrations/clickup/files/ai/tools/get-task.ts +0 -59
- package/dist/integrations/clickup/files/ai/tools/list-lists.ts +0 -109
- package/dist/integrations/clickup/files/ai/tools/list-tasks.ts +0 -95
- package/dist/integrations/clickup/files/ai/tools/update-task.ts +0 -85
- package/dist/integrations/clickup/files/app/api/auth/clickup/callback/route.ts +0 -31
- package/dist/integrations/clickup/files/app/api/auth/clickup/route.ts +0 -7
- package/dist/integrations/clickup/files/lib/clickup-client.ts +0 -439
- package/dist/integrations/confluence/README.md +0 -246
- package/dist/integrations/confluence/connector.json +0 -104
- package/dist/integrations/confluence/files/_env.example +0 -4
- package/dist/integrations/confluence/files/ai/tools/create-page.ts +0 -43
- package/dist/integrations/confluence/files/ai/tools/get-page.ts +0 -30
- package/dist/integrations/confluence/files/ai/tools/list-spaces.ts +0 -31
- package/dist/integrations/confluence/files/ai/tools/search-content.ts +0 -38
- package/dist/integrations/confluence/files/ai/tools/update-page.ts +0 -45
- package/dist/integrations/confluence/files/app/api/auth/confluence/callback/route.ts +0 -31
- package/dist/integrations/confluence/files/app/api/auth/confluence/route.ts +0 -9
- package/dist/integrations/confluence/files/lib/confluence-client.ts +0 -281
- package/dist/integrations/discord/connector.json +0 -100
- package/dist/integrations/discord/files/_env.example +0 -12
- package/dist/integrations/discord/files/ai/tools/get-messages.ts +0 -55
- package/dist/integrations/discord/files/ai/tools/get-user.ts +0 -32
- package/dist/integrations/discord/files/ai/tools/list-channels.ts +0 -32
- package/dist/integrations/discord/files/ai/tools/list-guilds.ts +0 -24
- package/dist/integrations/discord/files/ai/tools/send-message.ts +0 -33
- package/dist/integrations/discord/files/app/api/auth/discord/callback/route.ts +0 -31
- package/dist/integrations/discord/files/app/api/auth/discord/route.ts +0 -9
- package/dist/integrations/discord/files/lib/discord-client.ts +0 -273
- package/dist/integrations/docs-google/connector.json +0 -101
- package/dist/integrations/docs-google/files/_env.example +0 -8
- package/dist/integrations/docs-google/files/ai/tools/create-document.ts +0 -46
- package/dist/integrations/docs-google/files/ai/tools/get-document.ts +0 -46
- package/dist/integrations/docs-google/files/ai/tools/list-documents.ts +0 -42
- package/dist/integrations/docs-google/files/ai/tools/search-documents.ts +0 -38
- package/dist/integrations/docs-google/files/ai/tools/update-document.ts +0 -131
- package/dist/integrations/docs-google/files/app/api/auth/docs-google/callback/route.ts +0 -31
- package/dist/integrations/docs-google/files/app/api/auth/docs-google/route.ts +0 -9
- package/dist/integrations/docs-google/files/lib/docs-client.ts +0 -582
- package/dist/integrations/docs-google/files/lib/oauth.ts +0 -145
- package/dist/integrations/drive/connector.json +0 -134
- package/dist/integrations/drive/files/_env.example +0 -9
- package/dist/integrations/drive/files/ai/tools/create-folder.ts +0 -47
- package/dist/integrations/drive/files/ai/tools/get-file.ts +0 -55
- package/dist/integrations/drive/files/ai/tools/list-files.ts +0 -78
- package/dist/integrations/drive/files/ai/tools/search-files.ts +0 -79
- package/dist/integrations/drive/files/ai/tools/upload-file.ts +0 -59
- package/dist/integrations/drive/files/app/api/auth/drive/callback/route.ts +0 -31
- package/dist/integrations/drive/files/app/api/auth/drive/route.ts +0 -9
- package/dist/integrations/drive/files/lib/drive-client.ts +0 -359
- package/dist/integrations/drive/files/lib/oauth.ts +0 -145
- package/dist/integrations/dropbox/connector.json +0 -107
- package/dist/integrations/dropbox/files/_env.example +0 -24
- package/dist/integrations/dropbox/files/ai/tools/get-account.ts +0 -58
- package/dist/integrations/dropbox/files/ai/tools/get-file.ts +0 -61
- package/dist/integrations/dropbox/files/ai/tools/list-files.ts +0 -56
- package/dist/integrations/dropbox/files/ai/tools/search-files.ts +0 -70
- package/dist/integrations/dropbox/files/ai/tools/upload-file.ts +0 -48
- package/dist/integrations/dropbox/files/app/api/auth/dropbox/callback/route.ts +0 -31
- package/dist/integrations/dropbox/files/app/api/auth/dropbox/route.ts +0 -9
- package/dist/integrations/dropbox/files/lib/dropbox-client.ts +0 -397
- package/dist/integrations/figma/INTEGRATION_SUMMARY.md +0 -436
- package/dist/integrations/figma/README.md +0 -287
- package/dist/integrations/figma/connector.json +0 -100
- package/dist/integrations/figma/files/_env.example +0 -5
- package/dist/integrations/figma/files/ai/tools/get-comments.ts +0 -72
- package/dist/integrations/figma/files/ai/tools/get-file.ts +0 -54
- package/dist/integrations/figma/files/ai/tools/list-files.ts +0 -39
- package/dist/integrations/figma/files/ai/tools/list-projects.ts +0 -69
- package/dist/integrations/figma/files/ai/tools/post-comment.ts +0 -54
- package/dist/integrations/figma/files/app/api/auth/figma/callback/route.ts +0 -31
- package/dist/integrations/figma/files/app/api/auth/figma/route.ts +0 -9
- package/dist/integrations/figma/files/lib/figma-client.ts +0 -355
- package/dist/integrations/figma/files/lib/types.ts +0 -503
- package/dist/integrations/freshdesk/connector.json +0 -85
- package/dist/integrations/freshdesk/files/_env.example +0 -4
- package/dist/integrations/freshdesk/files/ai/tools/create-ticket.ts +0 -60
- package/dist/integrations/freshdesk/files/ai/tools/get-ticket.ts +0 -46
- package/dist/integrations/freshdesk/files/ai/tools/list-contacts.ts +0 -37
- package/dist/integrations/freshdesk/files/ai/tools/list-tickets.ts +0 -59
- package/dist/integrations/freshdesk/files/ai/tools/update-ticket.ts +0 -61
- package/dist/integrations/freshdesk/files/app/api/auth/freshdesk/callback/route.ts +0 -31
- package/dist/integrations/freshdesk/files/app/api/auth/freshdesk/route.ts +0 -7
- package/dist/integrations/freshdesk/files/lib/freshdesk-client.ts +0 -178
- package/dist/integrations/github/connector.json +0 -84
- package/dist/integrations/github/files/ai/tools/create-issue.ts +0 -71
- package/dist/integrations/github/files/ai/tools/get-pr-diff.ts +0 -78
- package/dist/integrations/github/files/ai/tools/list-prs.ts +0 -89
- package/dist/integrations/github/files/ai/tools/list-repos.ts +0 -77
- package/dist/integrations/github/files/app/api/auth/github/callback/route.ts +0 -31
- package/dist/integrations/github/files/app/api/auth/github/route.ts +0 -9
- package/dist/integrations/github/files/lib/github-client.ts +0 -282
- package/dist/integrations/gitlab/connector.json +0 -100
- package/dist/integrations/gitlab/files/_env.example +0 -7
- package/dist/integrations/gitlab/files/ai/tools/create-issue.ts +0 -49
- package/dist/integrations/gitlab/files/ai/tools/get-issue.ts +0 -56
- package/dist/integrations/gitlab/files/ai/tools/list-merge-requests.ts +0 -75
- package/dist/integrations/gitlab/files/ai/tools/list-projects.ts +0 -51
- package/dist/integrations/gitlab/files/ai/tools/search-issues.ts +0 -67
- package/dist/integrations/gitlab/files/app/api/auth/gitlab/callback/route.ts +0 -31
- package/dist/integrations/gitlab/files/app/api/auth/gitlab/route.ts +0 -9
- package/dist/integrations/gitlab/files/lib/gitlab-client.ts +0 -366
- package/dist/integrations/gmail/connector.json +0 -84
- package/dist/integrations/gmail/files/_env.example +0 -29
- package/dist/integrations/gmail/files/ai/tools/list-emails.ts +0 -88
- package/dist/integrations/gmail/files/ai/tools/search-emails.ts +0 -88
- package/dist/integrations/gmail/files/ai/tools/send-email.ts +0 -73
- package/dist/integrations/gmail/files/app/api/auth/gmail/callback/route.ts +0 -31
- package/dist/integrations/gmail/files/app/api/auth/gmail/route.ts +0 -11
- package/dist/integrations/gmail/files/lib/gmail-client.ts +0 -232
- package/dist/integrations/hubspot/connector.json +0 -98
- package/dist/integrations/hubspot/files/_env.example +0 -5
- package/dist/integrations/hubspot/files/ai/tools/create-contact.ts +0 -41
- package/dist/integrations/hubspot/files/ai/tools/create-deal.ts +0 -41
- package/dist/integrations/hubspot/files/ai/tools/get-contact.ts +0 -39
- package/dist/integrations/hubspot/files/ai/tools/list-contacts.ts +0 -43
- package/dist/integrations/hubspot/files/ai/tools/list-deals.ts +0 -41
- package/dist/integrations/hubspot/files/app/api/auth/hubspot/callback/route.ts +0 -31
- package/dist/integrations/hubspot/files/app/api/auth/hubspot/route.ts +0 -9
- package/dist/integrations/hubspot/files/lib/hubspot-client.ts +0 -393
- package/dist/integrations/intercom/connector.json +0 -85
- package/dist/integrations/intercom/files/_env.example +0 -4
- package/dist/integrations/intercom/files/ai/tools/get-contact.ts +0 -35
- package/dist/integrations/intercom/files/ai/tools/get-conversation.ts +0 -55
- package/dist/integrations/intercom/files/ai/tools/list-contacts.ts +0 -35
- package/dist/integrations/intercom/files/ai/tools/list-conversations.ts +0 -49
- package/dist/integrations/intercom/files/ai/tools/send-message.ts +0 -34
- package/dist/integrations/intercom/files/app/api/auth/intercom/callback/route.ts +0 -31
- package/dist/integrations/intercom/files/app/api/auth/intercom/route.ts +0 -7
- package/dist/integrations/intercom/files/lib/intercom-client.ts +0 -308
- package/dist/integrations/jira/connector.json +0 -109
- package/dist/integrations/jira/files/ai/tools/create-issue.ts +0 -47
- package/dist/integrations/jira/files/ai/tools/get-issue.ts +0 -57
- package/dist/integrations/jira/files/ai/tools/list-projects.ts +0 -30
- package/dist/integrations/jira/files/ai/tools/search-issues.ts +0 -49
- package/dist/integrations/jira/files/ai/tools/update-issue.ts +0 -81
- package/dist/integrations/jira/files/app/api/auth/jira/callback/route.ts +0 -31
- package/dist/integrations/jira/files/app/api/auth/jira/route.ts +0 -9
- package/dist/integrations/jira/files/lib/jira-client.ts +0 -338
- package/dist/integrations/linear/connector.json +0 -100
- package/dist/integrations/linear/files/_env.example +0 -6
- package/dist/integrations/linear/files/ai/tools/create-issue.ts +0 -71
- package/dist/integrations/linear/files/ai/tools/get-issue.ts +0 -55
- package/dist/integrations/linear/files/ai/tools/list-projects.ts +0 -43
- package/dist/integrations/linear/files/ai/tools/search-issues.ts +0 -54
- package/dist/integrations/linear/files/ai/tools/update-issue.ts +0 -71
- package/dist/integrations/linear/files/app/api/auth/linear/callback/route.ts +0 -31
- package/dist/integrations/linear/files/app/api/auth/linear/route.ts +0 -9
- package/dist/integrations/linear/files/lib/linear-client.ts +0 -464
- package/dist/integrations/mailchimp/connector.json +0 -85
- package/dist/integrations/mailchimp/files/_env.example +0 -4
- package/dist/integrations/mailchimp/files/ai/tools/get-campaign.ts +0 -45
- package/dist/integrations/mailchimp/files/ai/tools/get-list.ts +0 -51
- package/dist/integrations/mailchimp/files/ai/tools/list-campaigns.ts +0 -46
- package/dist/integrations/mailchimp/files/ai/tools/list-lists.ts +0 -46
- package/dist/integrations/mailchimp/files/ai/tools/list-members.ts +0 -58
- package/dist/integrations/mailchimp/files/app/api/auth/mailchimp/callback/route.ts +0 -31
- package/dist/integrations/mailchimp/files/app/api/auth/mailchimp/route.ts +0 -7
- package/dist/integrations/mailchimp/files/lib/mailchimp-client.ts +0 -267
- package/dist/integrations/mixpanel/connector.json +0 -96
- package/dist/integrations/mixpanel/files/_env.example +0 -11
- package/dist/integrations/mixpanel/files/ai/tools/get-funnel.ts +0 -46
- package/dist/integrations/mixpanel/files/ai/tools/get-retention.ts +0 -64
- package/dist/integrations/mixpanel/files/ai/tools/list-cohorts.ts +0 -46
- package/dist/integrations/mixpanel/files/ai/tools/query-events.ts +0 -43
- package/dist/integrations/mixpanel/files/ai/tools/track-event.ts +0 -41
- package/dist/integrations/mixpanel/files/lib/mixpanel-client.ts +0 -319
- package/dist/integrations/monday/connector.json +0 -85
- package/dist/integrations/monday/files/_env.example +0 -4
- package/dist/integrations/monday/files/ai/tools/create-item.ts +0 -36
- package/dist/integrations/monday/files/ai/tools/get-item.ts +0 -31
- package/dist/integrations/monday/files/ai/tools/list-boards.ts +0 -29
- package/dist/integrations/monday/files/ai/tools/list-items.ts +0 -36
- package/dist/integrations/monday/files/ai/tools/update-item.ts +0 -36
- package/dist/integrations/monday/files/app/api/auth/monday/callback/route.ts +0 -31
- package/dist/integrations/monday/files/app/api/auth/monday/route.ts +0 -7
- package/dist/integrations/monday/files/lib/monday-client.ts +0 -329
- package/dist/integrations/neon/connector.json +0 -89
- package/dist/integrations/neon/files/_env.example +0 -6
- package/dist/integrations/neon/files/ai/tools/describe-table.ts +0 -38
- package/dist/integrations/neon/files/ai/tools/list-branches.ts +0 -35
- package/dist/integrations/neon/files/ai/tools/list-projects.ts +0 -31
- package/dist/integrations/neon/files/ai/tools/list-tables.ts +0 -49
- package/dist/integrations/neon/files/ai/tools/query-database.ts +0 -33
- package/dist/integrations/neon/files/app/api/auth/neon/route.ts +0 -51
- package/dist/integrations/neon/files/lib/neon-client.ts +0 -294
- package/dist/integrations/notion/connector.json +0 -87
- package/dist/integrations/notion/files/_env.example +0 -6
- package/dist/integrations/notion/files/ai/tools/create-page.ts +0 -32
- package/dist/integrations/notion/files/ai/tools/query-database.ts +0 -44
- package/dist/integrations/notion/files/ai/tools/read-page.ts +0 -34
- package/dist/integrations/notion/files/ai/tools/search-notion.ts +0 -51
- package/dist/integrations/notion/files/app/api/auth/notion/callback/route.ts +0 -31
- package/dist/integrations/notion/files/app/api/auth/notion/route.ts +0 -9
- package/dist/integrations/notion/files/lib/notion-client.ts +0 -218
- package/dist/integrations/onedrive/connector.json +0 -100
- package/dist/integrations/onedrive/files/_env.example +0 -23
- package/dist/integrations/onedrive/files/ai/tools/download-file.ts +0 -38
- package/dist/integrations/onedrive/files/ai/tools/list-files.ts +0 -63
- package/dist/integrations/onedrive/files/ai/tools/search-files.ts +0 -59
- package/dist/integrations/onedrive/files/ai/tools/upload-file.ts +0 -43
- package/dist/integrations/onedrive/files/app/api/auth/onedrive/callback/route.ts +0 -31
- package/dist/integrations/onedrive/files/app/api/auth/onedrive/route.ts +0 -9
- package/dist/integrations/onedrive/files/lib/onedrive-client.ts +0 -314
- package/dist/integrations/outlook/README.md +0 -308
- package/dist/integrations/outlook/connector.json +0 -98
- package/dist/integrations/outlook/files/_env.example +0 -8
- package/dist/integrations/outlook/files/ai/tools/get-email.ts +0 -47
- package/dist/integrations/outlook/files/ai/tools/list-emails.ts +0 -46
- package/dist/integrations/outlook/files/ai/tools/list-folders.ts +0 -22
- package/dist/integrations/outlook/files/ai/tools/search-emails.ts +0 -41
- package/dist/integrations/outlook/files/ai/tools/send-email.ts +0 -41
- package/dist/integrations/outlook/files/app/api/auth/outlook/callback/route.ts +0 -31
- package/dist/integrations/outlook/files/app/api/auth/outlook/route.ts +0 -9
- package/dist/integrations/outlook/files/lib/outlook-client.ts +0 -204
- package/dist/integrations/pipedrive/connector.json +0 -85
- package/dist/integrations/pipedrive/files/_env.example +0 -4
- package/dist/integrations/pipedrive/files/ai/tools/create-deal.ts +0 -44
- package/dist/integrations/pipedrive/files/ai/tools/get-deal.ts +0 -34
- package/dist/integrations/pipedrive/files/ai/tools/list-deals.ts +0 -40
- package/dist/integrations/pipedrive/files/ai/tools/list-persons.ts +0 -33
- package/dist/integrations/pipedrive/files/ai/tools/update-deal.ts +0 -46
- package/dist/integrations/pipedrive/files/app/api/auth/pipedrive/callback/route.ts +0 -31
- package/dist/integrations/pipedrive/files/app/api/auth/pipedrive/route.ts +0 -7
- package/dist/integrations/pipedrive/files/lib/pipedrive-client.ts +0 -259
- package/dist/integrations/posthog/connector.json +0 -84
- package/dist/integrations/posthog/files/_env.example +0 -6
- package/dist/integrations/posthog/files/ai/tools/capture-event.ts +0 -37
- package/dist/integrations/posthog/files/ai/tools/get-trends.ts +0 -44
- package/dist/integrations/posthog/files/ai/tools/list-feature-flags.ts +0 -38
- package/dist/integrations/posthog/files/ai/tools/list-persons.ts +0 -32
- package/dist/integrations/posthog/files/lib/posthog-client.ts +0 -286
- package/dist/integrations/quickbooks/connector.json +0 -85
- package/dist/integrations/quickbooks/files/_env.example +0 -4
- package/dist/integrations/quickbooks/files/ai/tools/create-invoice.ts +0 -48
- package/dist/integrations/quickbooks/files/ai/tools/get-customer.ts +0 -36
- package/dist/integrations/quickbooks/files/ai/tools/get-invoice.ts +0 -46
- package/dist/integrations/quickbooks/files/ai/tools/list-customers.ts +0 -37
- package/dist/integrations/quickbooks/files/ai/tools/list-invoices.ts +0 -40
- package/dist/integrations/quickbooks/files/app/api/auth/quickbooks/callback/route.ts +0 -31
- package/dist/integrations/quickbooks/files/app/api/auth/quickbooks/route.ts +0 -7
- package/dist/integrations/quickbooks/files/lib/quickbooks-client.ts +0 -252
- package/dist/integrations/salesforce/connector.json +0 -104
- package/dist/integrations/salesforce/files/ai/tools/create-lead.ts +0 -101
- package/dist/integrations/salesforce/files/ai/tools/get-account.ts +0 -53
- package/dist/integrations/salesforce/files/ai/tools/list-accounts.ts +0 -50
- package/dist/integrations/salesforce/files/ai/tools/list-contacts.ts +0 -54
- package/dist/integrations/salesforce/files/ai/tools/list-opportunities.ts +0 -55
- package/dist/integrations/salesforce/files/app/api/auth/salesforce/callback/route.ts +0 -31
- package/dist/integrations/salesforce/files/app/api/auth/salesforce/route.ts +0 -9
- package/dist/integrations/salesforce/files/lib/salesforce-client.ts +0 -539
- package/dist/integrations/sentry/connector.json +0 -84
- package/dist/integrations/sentry/files/_env.example +0 -6
- package/dist/integrations/sentry/files/ai/tools/get-issue.ts +0 -66
- package/dist/integrations/sentry/files/ai/tools/list-issues.ts +0 -57
- package/dist/integrations/sentry/files/ai/tools/list-projects.ts +0 -32
- package/dist/integrations/sentry/files/ai/tools/resolve-issue.ts +0 -28
- package/dist/integrations/sentry/files/lib/sentry-client.ts +0 -268
- package/dist/integrations/servicenow/connector.json +0 -66
- package/dist/integrations/servicenow/files/_env.example +0 -5
- package/dist/integrations/servicenow/files/ai/tools/create-incident.ts +0 -58
- package/dist/integrations/servicenow/files/ai/tools/get-incident.ts +0 -59
- package/dist/integrations/servicenow/files/ai/tools/list-incidents.ts +0 -72
- package/dist/integrations/servicenow/files/ai/tools/search-knowledge.ts +0 -48
- package/dist/integrations/servicenow/files/ai/tools/update-incident.ts +0 -60
- package/dist/integrations/servicenow/files/app/api/auth/servicenow/callback/route.ts +0 -89
- package/dist/integrations/servicenow/files/app/api/auth/servicenow/route.ts +0 -42
- package/dist/integrations/servicenow/files/lib/servicenow-client.ts +0 -239
- package/dist/integrations/sharepoint/connector.json +0 -99
- package/dist/integrations/sharepoint/files/ai/tools/get-file.ts +0 -93
- package/dist/integrations/sharepoint/files/ai/tools/get-site.ts +0 -51
- package/dist/integrations/sharepoint/files/ai/tools/list-files.ts +0 -63
- package/dist/integrations/sharepoint/files/ai/tools/list-sites.ts +0 -28
- package/dist/integrations/sharepoint/files/ai/tools/upload-file.ts +0 -72
- package/dist/integrations/sharepoint/files/app/api/auth/sharepoint/callback/route.ts +0 -31
- package/dist/integrations/sharepoint/files/app/api/auth/sharepoint/route.ts +0 -9
- package/dist/integrations/sharepoint/files/lib/sharepoint-client.ts +0 -420
- package/dist/integrations/sheets/README.md +0 -331
- package/dist/integrations/sheets/connector.json +0 -99
- package/dist/integrations/sheets/files/_env.example +0 -8
- package/dist/integrations/sheets/files/ai/tools/create-spreadsheet.ts +0 -85
- package/dist/integrations/sheets/files/ai/tools/get-spreadsheet.ts +0 -39
- package/dist/integrations/sheets/files/ai/tools/list-spreadsheets.ts +0 -41
- package/dist/integrations/sheets/files/ai/tools/read-range.ts +0 -35
- package/dist/integrations/sheets/files/ai/tools/write-range.ts +0 -51
- package/dist/integrations/sheets/files/app/api/auth/sheets/callback/route.ts +0 -31
- package/dist/integrations/sheets/files/app/api/auth/sheets/route.ts +0 -9
- package/dist/integrations/sheets/files/lib/sheets-client.ts +0 -425
- package/dist/integrations/shopify/connector.json +0 -99
- package/dist/integrations/shopify/files/_env.example +0 -5
- package/dist/integrations/shopify/files/ai/tools/get-order.ts +0 -49
- package/dist/integrations/shopify/files/ai/tools/get-product.ts +0 -39
- package/dist/integrations/shopify/files/ai/tools/list-customers.ts +0 -40
- package/dist/integrations/shopify/files/ai/tools/list-orders.ts +0 -52
- package/dist/integrations/shopify/files/ai/tools/list-products.ts +0 -39
- package/dist/integrations/shopify/files/app/api/auth/shopify/callback/route.ts +0 -31
- package/dist/integrations/shopify/files/app/api/auth/shopify/route.ts +0 -7
- package/dist/integrations/shopify/files/lib/shopify-client.ts +0 -198
- package/dist/integrations/slack/connector.json +0 -74
- package/dist/integrations/slack/files/ai/tools/get-messages.ts +0 -61
- package/dist/integrations/slack/files/ai/tools/list-channels.ts +0 -59
- package/dist/integrations/slack/files/ai/tools/send-message.ts +0 -45
- package/dist/integrations/slack/files/app/api/auth/slack/callback/route.ts +0 -31
- package/dist/integrations/slack/files/app/api/auth/slack/route.ts +0 -9
- package/dist/integrations/slack/files/lib/slack-client.ts +0 -215
- package/dist/integrations/snowflake/connector.json +0 -151
- package/dist/integrations/snowflake/files/_env.example +0 -16
- package/dist/integrations/snowflake/files/ai/tools/describe-table.ts +0 -57
- package/dist/integrations/snowflake/files/ai/tools/list-databases.ts +0 -34
- package/dist/integrations/snowflake/files/ai/tools/list-schemas.ts +0 -40
- package/dist/integrations/snowflake/files/ai/tools/list-tables.ts +0 -49
- package/dist/integrations/snowflake/files/ai/tools/run-query.ts +0 -119
- package/dist/integrations/snowflake/files/lib/snowflake-client.ts +0 -389
- package/dist/integrations/stripe/connector.json +0 -97
- package/dist/integrations/stripe/files/_env.example +0 -6
- package/dist/integrations/stripe/files/ai/tools/get-balance.ts +0 -28
- package/dist/integrations/stripe/files/ai/tools/get-customer.ts +0 -26
- package/dist/integrations/stripe/files/ai/tools/list-customers.ts +0 -42
- package/dist/integrations/stripe/files/ai/tools/list-payments.ts +0 -45
- package/dist/integrations/stripe/files/ai/tools/list-subscriptions.ts +0 -67
- package/dist/integrations/stripe/files/app/api/auth/stripe/route.ts +0 -71
- package/dist/integrations/stripe/files/lib/stripe-client.ts +0 -376
- package/dist/integrations/supabase/connector.json +0 -101
- package/dist/integrations/supabase/files/_env.example +0 -6
- package/dist/integrations/supabase/files/ai/tools/delete-row.ts +0 -77
- package/dist/integrations/supabase/files/ai/tools/insert-row.ts +0 -35
- package/dist/integrations/supabase/files/ai/tools/list-tables.ts +0 -60
- package/dist/integrations/supabase/files/ai/tools/query-table.ts +0 -48
- package/dist/integrations/supabase/files/ai/tools/update-row.ts +0 -64
- package/dist/integrations/supabase/files/app/api/auth/supabase/route.ts +0 -91
- package/dist/integrations/supabase/files/lib/supabase-client.ts +0 -296
- package/dist/integrations/teams/README.md +0 -256
- package/dist/integrations/teams/connector.json +0 -99
- package/dist/integrations/teams/files/ai/tools/get-messages.ts +0 -55
- package/dist/integrations/teams/files/ai/tools/list-channels.ts +0 -28
- package/dist/integrations/teams/files/ai/tools/list-chats.ts +0 -41
- package/dist/integrations/teams/files/ai/tools/list-teams.ts +0 -27
- package/dist/integrations/teams/files/ai/tools/send-message.ts +0 -61
- package/dist/integrations/teams/files/app/api/auth/teams/callback/route.ts +0 -31
- package/dist/integrations/teams/files/app/api/auth/teams/route.ts +0 -9
- package/dist/integrations/teams/files/lib/teams-client.ts +0 -345
- package/dist/integrations/trello/connector.json +0 -85
- package/dist/integrations/trello/files/_env.example +0 -4
- package/dist/integrations/trello/files/ai/tools/create-card.ts +0 -54
- package/dist/integrations/trello/files/ai/tools/get-card.ts +0 -33
- package/dist/integrations/trello/files/ai/tools/list-boards.ts +0 -29
- package/dist/integrations/trello/files/ai/tools/list-cards.ts +0 -52
- package/dist/integrations/trello/files/ai/tools/update-card.ts +0 -65
- package/dist/integrations/trello/files/app/api/auth/trello/callback/route.ts +0 -31
- package/dist/integrations/trello/files/app/api/auth/trello/route.ts +0 -7
- package/dist/integrations/trello/files/lib/trello-client.ts +0 -202
- package/dist/integrations/twilio/connector.json +0 -146
- package/dist/integrations/twilio/files/_env.example +0 -14
- package/dist/integrations/twilio/files/ai/tools/get-message.ts +0 -58
- package/dist/integrations/twilio/files/ai/tools/list-calls.ts +0 -129
- package/dist/integrations/twilio/files/ai/tools/list-messages.ts +0 -97
- package/dist/integrations/twilio/files/ai/tools/send-sms.ts +0 -75
- package/dist/integrations/twilio/files/ai/tools/send-whatsapp.ts +0 -81
- package/dist/integrations/twilio/files/lib/twilio-client.ts +0 -375
- package/dist/integrations/twitter/connector.json +0 -87
- package/dist/integrations/twitter/files/_env.example +0 -6
- package/dist/integrations/twitter/files/ai/tools/get-timeline.ts +0 -59
- package/dist/integrations/twitter/files/ai/tools/post-tweet.ts +0 -49
- package/dist/integrations/twitter/files/ai/tools/search-tweets.ts +0 -71
- package/dist/integrations/twitter/files/app/api/auth/twitter/callback/route.ts +0 -31
- package/dist/integrations/twitter/files/app/api/auth/twitter/route.ts +0 -9
- package/dist/integrations/twitter/files/lib/twitter-client.ts +0 -236
- package/dist/integrations/webex/connector.json +0 -85
- package/dist/integrations/webex/files/_env.example +0 -4
- package/dist/integrations/webex/files/ai/tools/create-meeting.ts +0 -69
- package/dist/integrations/webex/files/ai/tools/get-meeting.ts +0 -31
- package/dist/integrations/webex/files/ai/tools/list-meetings.ts +0 -44
- package/dist/integrations/webex/files/ai/tools/list-rooms.ts +0 -35
- package/dist/integrations/webex/files/ai/tools/send-message.ts +0 -51
- package/dist/integrations/webex/files/app/api/auth/webex/callback/route.ts +0 -31
- package/dist/integrations/webex/files/app/api/auth/webex/route.ts +0 -7
- package/dist/integrations/webex/files/lib/webex-client.ts +0 -279
- package/dist/integrations/xero/connector.json +0 -85
- package/dist/integrations/xero/files/_env.example +0 -4
- package/dist/integrations/xero/files/ai/tools/create-invoice.ts +0 -65
- package/dist/integrations/xero/files/ai/tools/get-contact.ts +0 -40
- package/dist/integrations/xero/files/ai/tools/get-invoice.ts +0 -44
- package/dist/integrations/xero/files/ai/tools/list-contacts.ts +0 -54
- package/dist/integrations/xero/files/ai/tools/list-invoices.ts +0 -54
- package/dist/integrations/xero/files/app/api/auth/xero/callback/route.ts +0 -31
- package/dist/integrations/xero/files/app/api/auth/xero/route.ts +0 -7
- package/dist/integrations/xero/files/lib/xero-client.ts +0 -292
- package/dist/integrations/zendesk/connector.json +0 -61
- package/dist/integrations/zendesk/files/_env.example +0 -5
- package/dist/integrations/zendesk/files/ai/tools/create-ticket.ts +0 -82
- package/dist/integrations/zendesk/files/ai/tools/get-ticket.ts +0 -53
- package/dist/integrations/zendesk/files/ai/tools/list-tickets.ts +0 -60
- package/dist/integrations/zendesk/files/ai/tools/search-tickets.ts +0 -56
- package/dist/integrations/zendesk/files/app/api/auth/zendesk/callback/route.ts +0 -91
- package/dist/integrations/zendesk/files/app/api/auth/zendesk/route.ts +0 -41
- package/dist/integrations/zendesk/files/lib/zendesk-client.ts +0 -265
- package/dist/integrations/zoom/connector.json +0 -85
- package/dist/integrations/zoom/files/_env.example +0 -4
- package/dist/integrations/zoom/files/ai/tools/create-meeting.ts +0 -106
- package/dist/integrations/zoom/files/ai/tools/delete-meeting.ts +0 -32
- package/dist/integrations/zoom/files/ai/tools/get-meeting.ts +0 -44
- package/dist/integrations/zoom/files/ai/tools/list-meetings.ts +0 -47
- package/dist/integrations/zoom/files/ai/tools/update-meeting.ts +0 -111
- package/dist/integrations/zoom/files/app/api/auth/zoom/callback/route.ts +0 -31
- package/dist/integrations/zoom/files/app/api/auth/zoom/route.ts +0 -7
- package/dist/integrations/zoom/files/lib/zoom-client.ts +0 -228
- package/dist/oauth/handlers.js +0 -576
- package/dist/oauth/handlers.js.map +0 -7
- package/dist/oauth/index.js +0 -1179
- package/dist/oauth/index.js.map +0 -7
- package/dist/oauth/providers.js +0 -951
- package/dist/oauth/providers.js.map +0 -7
- package/dist/oauth/token-store.js +0 -101
- package/dist/oauth/token-store.js.map +0 -7
- package/dist/router.d.ts +0 -69
- package/dist/router.js +0 -57
- package/dist/router.js.map +0 -7
- package/dist/templates/ai/ai/agents/assistant.ts +0 -22
- package/dist/templates/ai/ai/prompts/assistant.ts +0 -15
- package/dist/templates/ai/ai/tools/.gitkeep +0 -0
- package/dist/templates/ai/app/api/chat/route.ts +0 -147
- package/dist/templates/ai/app/layout.tsx +0 -11
- package/dist/templates/ai/app/page.tsx +0 -28
- package/dist/templates/ai/tsconfig.json +0 -19
- package/dist/templates/ai/veryfront.config.ts +0 -9
- package/dist/templates/app/_env.example +0 -16
- package/dist/templates/app/app/api/auth/login/route.ts +0 -53
- package/dist/templates/app/app/api/auth/logout/route.ts +0 -27
- package/dist/templates/app/app/api/auth/me/route.ts +0 -34
- package/dist/templates/app/app/api/auth/register/route.ts +0 -42
- package/dist/templates/app/app/api/stats/route.ts +0 -21
- package/dist/templates/app/app/api/users/route.ts +0 -42
- package/dist/templates/app/app/dashboard/page.tsx +0 -29
- package/dist/templates/app/app/layout.tsx +0 -23
- package/dist/templates/app/app/login/page.tsx +0 -150
- package/dist/templates/app/app/page.tsx +0 -15
- package/dist/templates/app/components/AuthProvider.tsx +0 -51
- package/dist/templates/app/components/DashboardLayout.tsx +0 -76
- package/dist/templates/app/components/FeatureGrid.tsx +0 -92
- package/dist/templates/app/components/Header.tsx +0 -55
- package/dist/templates/app/components/HeroSection.tsx +0 -39
- package/dist/templates/app/components/RecentActivity.tsx +0 -98
- package/dist/templates/app/components/StatsGrid.tsx +0 -126
- package/dist/templates/app/components/Toaster.tsx +0 -113
- package/dist/templates/app/lib/auth-client.ts +0 -38
- package/dist/templates/app/lib/auth.ts +0 -49
- package/dist/templates/app/lib/stats.ts +0 -34
- package/dist/templates/app/lib/users.ts +0 -86
- package/dist/templates/app/middleware/auth.ts +0 -34
- package/dist/templates/app/public/robots.txt +0 -4
- package/dist/templates/app/veryfront.config.js +0 -73
- package/dist/templates/blog/app/about/page.mdx +0 -14
- package/dist/templates/blog/app/archive/page.tsx +0 -42
- package/dist/templates/blog/app/blog/[slug]/page.tsx +0 -47
- package/dist/templates/blog/app/layout.tsx +0 -45
- package/dist/templates/blog/app/page.tsx +0 -13
- package/dist/templates/blog/components/BlogPostList.tsx +0 -44
- package/dist/templates/blog/components/MDXContent.tsx +0 -26
- package/dist/templates/blog/content/posts/hello-world.mdx +0 -29
- package/dist/templates/blog/content/posts/markdown-showcase.mdx +0 -105
- package/dist/templates/blog/lib/posts.ts +0 -76
- package/dist/templates/blog/lib/utils.ts +0 -14
- package/dist/templates/blog/public/robots.txt +0 -4
- package/dist/templates/blog/styles/globals.css +0 -21
- package/dist/templates/blog/veryfront.config.js +0 -38
- package/dist/templates/docs/app/docs/api/page.mdx +0 -102
- package/dist/templates/docs/app/docs/core-concepts/page.mdx +0 -82
- package/dist/templates/docs/app/docs/getting-started/page.mdx +0 -67
- package/dist/templates/docs/app/layout.tsx +0 -28
- package/dist/templates/docs/app/page.mdx +0 -51
- package/dist/templates/docs/components/CodeBlock.tsx +0 -44
- package/dist/templates/docs/components/Header.tsx +0 -52
- package/dist/templates/docs/components/Sidebar.tsx +0 -68
- package/dist/templates/docs/public/robots.txt +0 -4
- package/dist/templates/docs/styles/globals.css +0 -48
- package/dist/templates/docs/veryfront.config.js +0 -46
- package/dist/templates/minimal/app/about/page.mdx +0 -18
- package/dist/templates/minimal/app/layout.tsx +0 -13
- package/dist/templates/minimal/app/page.tsx +0 -26
- package/dist/templates/minimal/veryfront.config.js +0 -29
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
"version": 1,
|
|
3
|
+
"templates": {
|
|
4
|
+
"ai": {
|
|
5
|
+
"files": {
|
|
6
|
+
"tools/calculator.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\n\nexport default tool({\n id: \"calculator\",\n description: \"Perform basic arithmetic operations\",\n parameters: z.object({\n operation: z.enum([\"add\", \"subtract\", \"multiply\", \"divide\"]),\n a: z.number(),\n b: z.number(),\n }),\n execute: async ({ operation, a, b }) => {\n if (operation === \"divide\" && b === 0) {\n throw new Error(\"Cannot divide by zero\");\n }\n\n switch (operation) {\n case \"add\":\n return { result: a + b };\n case \"subtract\":\n return { result: a - b };\n case \"multiply\":\n return { result: a * b };\n case \"divide\":\n return { result: a / b };\n }\n },\n});\n",
|
|
7
|
+
"app/api/chat/route.ts": "import { z } from \"zod\";\nimport { getAgent } from \"veryfront/agent\";\n\nconst textPartSchema = z.object({\n type: z.literal(\"text\"),\n text: z.string().max(10000),\n state: z.string().optional(),\n});\n\nconst toolCallPartSchema = z.object({\n type: z.literal(\"tool-call\"),\n toolCallId: z.string(),\n toolName: z.string(),\n args: z.unknown(),\n});\n\nconst toolResultPartSchema = z.object({\n type: z.literal(\"tool-result\"),\n toolCallId: z.string(),\n result: z.unknown(),\n});\n\nconst dynamicToolPartSchema = z\n .object({\n type: z.string().startsWith(\"tool-\"),\n toolCallId: z.string(),\n toolName: z.string(),\n state: z.string().optional(),\n input: z.unknown().optional(),\n output: z.unknown().optional(),\n })\n .passthrough();\n\nconst partSchema = z.union([\n textPartSchema,\n toolCallPartSchema,\n toolResultPartSchema,\n dynamicToolPartSchema,\n]);\n\nconst messageSchema = z.object({\n id: z.string().optional(),\n role: z.enum([\"user\", \"assistant\", \"system\", \"tool\"]),\n parts: z.array(partSchema).min(1),\n});\n\nconst chatRequestSchema = z.object({\n messages: z.array(messageSchema).min(1).max(100),\n});\n\ntype ParsedMessage = z.infer<typeof messageSchema>;\n\ntype ToolPartWithOutput = {\n type: string;\n toolCallId: string;\n toolName: string;\n output: unknown;\n};\n\nfunction isToolPartWithOutput(part: unknown): part is ToolPartWithOutput {\n if (!part || typeof part !== \"object\") return false;\n\n const p = part as Record<string, unknown>;\n const type = p.type;\n\n if (typeof type !== \"string\") return false;\n if (!type.startsWith(\"tool-\") || type === \"tool-result\") return false;\n\n return \"output\" in p && p.output !== undefined;\n}\n\n/**\n * Transform UI messages to agent-compatible format.\n * AI SDK v5 UI bundles tool results in assistant message parts (output field),\n * but the agent runtime expects separate tool role messages.\n */\nfunction transformUIMessages(messages: ParsedMessage[]): ParsedMessage[] {\n const result: ParsedMessage[] = [];\n\n for (const msg of messages) {\n if (msg.role !== \"assistant\") {\n result.push(msg);\n continue;\n }\n\n const toolPartsWithOutput = msg.parts.filter(isToolPartWithOutput);\n\n result.push(msg);\n\n for (const toolPart of toolPartsWithOutput) {\n result.push({\n id: `tool_${toolPart.toolCallId}`,\n role: \"tool\",\n parts: [\n {\n type: \"tool-result\",\n toolCallId: toolPart.toolCallId,\n result: toolPart.output,\n },\n ],\n });\n }\n }\n\n return result;\n}\n\nexport async function POST(request: Request): Promise<Response> {\n try {\n const body = await request.json();\n const { messages: rawMessages } = chatRequestSchema.parse(body);\n\n const messages = transformUIMessages(rawMessages);\n\n const agent = getAgent(\"assistant\");\n if (!agent) return Response.json({ error: \"Agent not found\" }, { status: 404 });\n\n // Clear server-side memory before each request\n // The client (useChat) manages full conversation history\n await agent.clearMemory();\n\n // In production, extract userId from session/cookie\n // For development, we use a default user\n const userId = \"current-user\";\n\n const result = await agent.stream({\n messages,\n context: { userId },\n });\n\n return result.toDataStreamResponse();\n } catch (error) {\n if (error instanceof z.ZodError) {\n return Response.json({ error: \"Invalid request\", details: error.errors }, { status: 400 });\n }\n\n return Response.json({ error: \"Internal server error\" }, { status: 500 });\n }\n}\n",
|
|
8
|
+
"app/layout.tsx": "export default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.ReactNode {\n return (\n <html lang=\"en\">\n <head>\n <meta charSet=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>AI Chat</title>\n </head>\n <body>{children}</body>\n </html>\n );\n}\n",
|
|
9
|
+
"app/page.tsx": "'use client'\n\nimport { Chat } from 'veryfront/components/ai'\nimport { useChat } from 'veryfront/agent/react'\n\nexport default function ChatPage(): JSX.Element {\n const chat = useChat({ api: '/api/chat' })\n\n return (\n <div className=\"flex flex-col h-screen bg-white dark:bg-neutral-900\">\n <header className=\"sticky top-0 z-10 flex-shrink-0 border-b border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900\">\n <div className=\"flex items-center justify-between px-4 py-3\">\n <h1 className=\"font-medium text-neutral-900 dark:text-white\">AI Assistant</h1>\n </div>\n </header>\n\n <Chat {...chat} className=\"flex-1 min-h-0\" placeholder=\"Message\" />\n </div>\n )\n}\n",
|
|
10
|
+
"agents/assistant.ts": "import { agent } from \"veryfront/agent\";\nimport { promptRegistry } from \"veryfront/prompt\";\n\nfunction getSystemPrompt(): string {\n const content = promptRegistry.get(\"assistant\")?.getContent();\n if (typeof content === \"string\") return content;\n return \"You answer questions for this template.\";\n}\n\nexport default agent({\n id: \"assistant\",\n model: \"openai/gpt-4o\",\n system: getSystemPrompt,\n tools: true,\n maxSteps: 10,\n});\n",
|
|
11
|
+
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"exclude\": [\"node_modules\"]\n}\n"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"minimal": {
|
|
15
|
+
"files": {
|
|
16
|
+
"app/layout.tsx": "export default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.ReactNode {\n return (\n <div className=\"min-h-screen bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100\">\n <main className=\"max-w-2xl mx-auto px-6 py-16\">{children}</main>\n </div>\n );\n}\n",
|
|
17
|
+
"app/about/page.mdx": "# About\n\nThis is a minimal starter template.\n\n## Features\n\n- HMR for local development\n- MDX support\n- Tailwind CSS\n- Minimal defaults\n\n## Getting Started\n\n1. Edit pages in the `app` directory\n2. Add components in `components`\n3. Configure your app in `veryfront.config.js`\n\nHappy coding!\n",
|
|
18
|
+
"app/page.tsx": "export default function HomePage(): JSX.Element {\n return (\n <div>\n <h1 className=\"mb-4 text-4xl font-bold text-neutral-900 dark:text-white\">\n Welcome to Veryfront\n </h1>\n <p className=\"mb-8 text-neutral-600 dark:text-neutral-400\">\n Edit{\" \"}\n <code className=\"rounded bg-neutral-100 px-1.5 py-0.5 text-sm dark:bg-neutral-800\">\n app/page.tsx\n </code>{\" \"}\n to get started.\n </p>\n <div className=\"flex gap-3\">\n <a\n href=\"/about\"\n className=\"rounded-full bg-blue-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-600\"\n >\n About\n </a>\n <a\n href=\"https://veryfront.com/docs\"\n className=\"rounded-full bg-neutral-100 px-4 py-2 text-sm font-medium text-neutral-900 transition-colors hover:bg-neutral-200 dark:bg-neutral-800 dark:text-white dark:hover:bg-neutral-700\"\n >\n Documentation\n </a>\n </div>\n </div>\n );\n}\n"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"app": {
|
|
22
|
+
"files": {
|
|
23
|
+
"api/chat/route.ts": "import { z } from \"zod\";\nimport { getAgent } from \"veryfront/agent\";\n\nconst textPartSchema = z.object({\n type: z.literal(\"text\"),\n text: z.string().max(10000),\n state: z.string().optional(),\n});\n\nconst toolCallPartSchema = z.object({\n type: z.literal(\"tool-call\"),\n toolCallId: z.string(),\n toolName: z.string(),\n args: z.unknown(),\n});\n\nconst toolResultPartSchema = z.object({\n type: z.literal(\"tool-result\"),\n toolCallId: z.string(),\n result: z.unknown(),\n});\n\nconst dynamicToolPartSchema = z\n .object({\n type: z.string().startsWith(\"tool-\"),\n toolCallId: z.string(),\n toolName: z.string(),\n state: z.string().optional(),\n input: z.unknown().optional(),\n output: z.unknown().optional(),\n })\n .passthrough();\n\nconst partSchema = z.union([\n textPartSchema,\n toolCallPartSchema,\n toolResultPartSchema,\n dynamicToolPartSchema,\n]);\n\nconst messageSchema = z.object({\n id: z.string().optional(),\n role: z.enum([\"user\", \"assistant\", \"system\", \"tool\"]),\n parts: z.array(partSchema).min(1),\n});\n\nconst chatRequestSchema = z.object({\n messages: z.array(messageSchema).min(1).max(100),\n});\n\ntype ParsedMessage = z.infer<typeof messageSchema>;\n\ntype ToolPartWithOutput = {\n type: string;\n toolCallId: string;\n toolName: string;\n output: unknown;\n};\n\nfunction isToolPartWithOutput(part: unknown): part is ToolPartWithOutput {\n if (!part || typeof part !== \"object\") return false;\n\n const p = part as Record<string, unknown>;\n const type = p.type;\n\n if (typeof type !== \"string\") return false;\n if (!type.startsWith(\"tool-\") || type === \"tool-result\") return false;\n\n return p.output !== undefined;\n}\n\nfunction transformUIMessages(messages: ParsedMessage[]): ParsedMessage[] {\n const result: ParsedMessage[] = [];\n\n for (const msg of messages) {\n if (msg.role !== \"assistant\") {\n result.push(msg);\n continue;\n }\n\n const toolPartsWithOutput = msg.parts.filter(isToolPartWithOutput);\n\n result.push(msg);\n\n for (const toolPart of toolPartsWithOutput) {\n result.push({\n id: `tool_${toolPart.toolCallId}`,\n role: \"tool\",\n parts: [\n {\n type: \"tool-result\",\n toolCallId: toolPart.toolCallId,\n result: toolPart.output,\n },\n ],\n });\n }\n }\n\n return result;\n}\n\nexport async function POST(request: Request): Promise<Response> {\n try {\n const body = await request.json();\n const { messages: rawMessages } = chatRequestSchema.parse(body);\n const messages = transformUIMessages(rawMessages);\n\n const agent = getAgent(\"assistant\");\n if (!agent) return Response.json({ error: \"Agent not found\" }, { status: 404 });\n\n await agent.clearMemory();\n\n const userId = \"current-user\";\n const result = await agent.stream({ messages, context: { userId } });\n\n return result.toDataStreamResponse();\n } catch (error) {\n if (error instanceof z.ZodError) {\n return Response.json({ error: \"Invalid request\", details: error.errors }, { status: 400 });\n }\n return Response.json({ error: \"Internal server error\" }, { status: 500 });\n }\n}\n",
|
|
24
|
+
"middleware/auth.ts": "import { verifySession } from \"../lib/auth.ts\";\n\nexport async function requireAuth(\n request: Request\n): Promise<\n | { ok: true; session: Awaited<ReturnType<typeof verifySession>> }\n | { ok: false; response: Response }\n> {\n const cookie = request.headers.get(\"cookie\");\n const token = cookie\n ?.split(\"; \")\n .find((row) => row.startsWith(\"session=\"))\n ?.split(\"=\")[1];\n\n if (!token) {\n return {\n ok: false,\n response: Response.json(\n { error: \"Authentication required\" },\n { status: 401 }\n ),\n };\n }\n\n const session = await verifySession(token);\n if (!session) {\n return {\n ok: false,\n response: Response.json({ error: \"Invalid session\" }, { status: 401 }),\n };\n }\n\n return { ok: true, session };\n}\n",
|
|
25
|
+
"components/HeroSection.tsx": "export function HeroSection(): JSX.Element {\n return (\n <section className=\"py-24 md:py-32\">\n <div className=\"max-w-5xl mx-auto px-6\">\n <div className=\"text-center max-w-3xl mx-auto\">\n <p className=\"text-sm font-medium text-blue-500 mb-4\">\n Starter template\n </p>\n\n <h1 className=\"text-4xl md:text-6xl font-bold tracking-tight text-neutral-900 dark:text-white mb-6\">\n Build your app\n </h1>\n\n <p className=\"text-lg text-neutral-600 dark:text-neutral-400 mb-10 max-w-xl mx-auto\">\n Full-stack starter with auth, API routes, and UI scaffolding you can\n customize.\n </p>\n\n <div className=\"flex flex-col sm:flex-row gap-4 justify-center\">\n <a\n href=\"/register\"\n className=\"px-6 py-3 bg-blue-500 text-white rounded-full font-medium hover:bg-blue-600 transition-colors\"\n >\n Get started\n </a>\n <a\n href=\"/docs\"\n className=\"px-6 py-3 bg-neutral-100 dark:bg-neutral-800 text-neutral-900 dark:text-white rounded-full font-medium hover:bg-neutral-200 dark:hover:bg-neutral-700 transition-colors\"\n >\n Documentation\n </a>\n </div>\n </div>\n </div>\n </section>\n );\n}\n",
|
|
26
|
+
"components/Toaster.tsx": "'use client';\n\nimport { useEffect, useState } from 'react';\n\ninterface Toast {\n id: string;\n message: string;\n type: 'success' | 'error' | 'info';\n}\n\nlet toastListener: ((toast: Toast) => void) | null = null;\n\nexport function showToast(message: string, type: Toast['type'] = 'info'): void {\n toastListener?.({\n id: Math.random().toString(36),\n message,\n type,\n });\n}\n\nfunction CheckCircleIcon({ className }: { className?: string }): React.JSX.Element {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\" />\n <polyline points=\"22 4 12 14.01 9 11.01\" />\n </svg>\n );\n}\n\nfunction AlertCircleIcon({ className }: { className?: string }): React.JSX.Element {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\" />\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\" />\n </svg>\n );\n}\n\nfunction InfoIcon({ className }: { className?: string }): React.JSX.Element {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\" />\n <line x1=\"12\" y1=\"8\" x2=\"12.01\" y2=\"8\" />\n </svg>\n );\n}\n\nfunction XIcon({ className }: { className?: string }): React.JSX.Element {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n );\n}\n\nfunction getToastClasses(type: Toast['type']): string {\n const base =\n 'pointer-events-auto flex items-start gap-3 px-4 py-3 rounded-xl shadow-lg backdrop-blur-md border transition-all animate-in slide-in-from-right-full fade-in duration-300 max-w-sm w-full';\n\n if (type === 'success') {\n return `${base} bg-white/90 dark:bg-slate-800/90 border-emerald-200 dark:border-emerald-800 text-emerald-800 dark:text-emerald-200`;\n }\n\n if (type === 'error') {\n return `${base} bg-white/90 dark:bg-slate-800/90 border-red-200 dark:border-red-800 text-red-800 dark:text-red-200`;\n }\n\n return `${base} bg-white/90 dark:bg-slate-800/90 border-blue-200 dark:border-blue-800 text-blue-800 dark:text-blue-200`;\n}\n\nfunction ToastIcon({ type }: { type: Toast['type'] }): React.JSX.Element {\n if (type === 'success') return <CheckCircleIcon className=\"w-5 h-5\" />;\n if (type === 'error') return <AlertCircleIcon className=\"w-5 h-5\" />;\n return <InfoIcon className=\"w-5 h-5\" />;\n}\n\nexport function Toaster(): React.JSX.Element {\n const [toasts, setToasts] = useState<Toast[]>([]);\n\n useEffect(() => {\n toastListener = (toast: Toast) => {\n setToasts((prev) => [...prev, toast]);\n\n setTimeout(() => {\n setToasts((prev) => prev.filter((t) => t.id !== toast.id));\n }, 4000);\n };\n\n return () => {\n toastListener = null;\n };\n }, []);\n\n function removeToast(id: string): void {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }\n\n return (\n <div className=\"fixed bottom-4 right-4 z-50 space-y-3 pointer-events-none\">\n {toasts.map((toast) => (\n <div key={toast.id} className={getToastClasses(toast.type)}>\n <div className=\"flex-shrink-0 mt-0.5\">\n <ToastIcon type={toast.type} />\n </div>\n\n <div className=\"flex-1 text-sm font-medium\">{toast.message}</div>\n\n <button\n onClick={() => removeToast(toast.id)}\n className=\"flex-shrink-0 text-current opacity-60 hover:opacity-100 transition-opacity\"\n >\n <XIcon className=\"w-4 h-4\" />\n </button>\n </div>\n ))}\n </div>\n );\n}\n",
|
|
27
|
+
"components/AuthProvider.tsx": "'use client';\n\nimport React, { createContext, useContext, useEffect, useState } from 'react';\nimport { logout as logoutUser } from '../lib/auth-client.ts';\n\ninterface User {\n id: string;\n email: string;\n name: string;\n role: string;\n}\n\ninterface AuthContextType {\n user: User | null;\n loading: boolean;\n logout: () => void;\n}\n\nconst AuthContext = createContext<AuthContextType>({\n user: null,\n loading: true,\n logout: () => {},\n});\n\nexport function AuthProvider({ children }: { children: React.ReactNode }): React.ReactNode {\n const [user, setUser] = useState<User | null>(null);\n const [loading, setLoading] = useState<boolean>(true);\n\n useEffect(() => {\n let active = true;\n\n async function loadUser(): Promise<void> {\n try {\n const res = await fetch('/api/auth/me');\n if (!res.ok) return;\n\n const data = await res.json();\n if (active && data?.user) setUser(data.user);\n } finally {\n if (active) setLoading(false);\n }\n }\n\n loadUser();\n\n return () => {\n active = false;\n };\n }, []);\n\n function logout(): void {\n logoutUser();\n setUser(null);\n }\n\n return <AuthContext.Provider value={{ user, loading, logout }}>{children}</AuthContext.Provider>;\n}\n\nexport function useAuth(): AuthContextType {\n return useContext(AuthContext);\n}\n",
|
|
28
|
+
"components/StatsGrid.tsx": "import { getStats } from \"../lib/stats.ts\";\n\ntype IconProps = { className?: string };\n\nfunction UsersIcon({ className }: IconProps): JSX.Element {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\" />\n <circle cx=\"9\" cy=\"7\" r=\"4\" />\n <path d=\"M22 21v-2a4 4 0 0 0-3-3.87\" />\n <path d=\"M16 3.13a4 4 0 0 1 0 7.75\" />\n </svg>\n );\n}\n\nfunction ActivityIcon({ className }: IconProps): JSX.Element {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <polyline points=\"22 12 18 12 15 21 9 3 6 12 2 12\" />\n </svg>\n );\n}\n\nfunction DollarIcon({ className }: IconProps): JSX.Element {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"23\" />\n <path d=\"M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6\" />\n </svg>\n );\n}\n\nfunction TrendingUpIcon({ className }: IconProps): JSX.Element {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <polyline points=\"23 6 13.5 15.5 8.5 10.5 1 18\" />\n <polyline points=\"17 6 23 6 23 12\" />\n </svg>\n );\n}\n\nfunction TrendingDownIcon({ className }: IconProps): JSX.Element {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <polyline points=\"23 18 13.5 8.5 8.5 13.5 1 6\" />\n <polyline points=\"17 18 23 18 23 12\" />\n </svg>\n );\n}\n\ntype StatsGridProps = { userId: string };\n\nexport async function StatsGrid({ userId }: StatsGridProps): Promise<JSX.Element> {\n const stats = await getStats(userId);\n\n const items = [\n {\n label: \"Total Users\",\n value: stats.totalUsers.toLocaleString(),\n change: \"+12%\",\n trend: \"up\",\n Icon: UsersIcon,\n color: \"from-blue-500 to-cyan-500\",\n bgColor: \"bg-blue-500/10\",\n shadowColor: \"shadow-blue-500/20\",\n },\n {\n label: \"Active Today\",\n value: stats.activeToday.toLocaleString(),\n change: \"+5%\",\n trend: \"up\",\n Icon: ActivityIcon,\n color: \"from-emerald-500 to-teal-500\",\n bgColor: \"bg-emerald-500/10\",\n shadowColor: \"shadow-emerald-500/20\",\n },\n {\n label: \"Revenue\",\n value: `$${stats.revenue.toLocaleString()}`,\n change: \"+8%\",\n trend: \"up\",\n Icon: DollarIcon,\n color: \"from-violet-500 to-purple-500\",\n bgColor: \"bg-violet-500/10\",\n shadowColor: \"shadow-violet-500/20\",\n },\n {\n label: \"Growth Rate\",\n value: `${stats.growth}%`,\n change: \"+2.3%\",\n trend: \"up\",\n Icon: TrendingUpIcon,\n color: \"from-orange-500 to-amber-500\",\n bgColor: \"bg-orange-500/10\",\n shadowColor: \"shadow-orange-500/20\",\n },\n ] as const;\n\n return (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6\">\n {items.map((item) => {\n const isUp = item.trend === \"up\";\n const trendClassName = isUp\n ? \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400\"\n : \"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400\";\n const TrendIcon = isUp ? TrendingUpIcon : TrendingDownIcon;\n\n return (\n <div\n key={item.label}\n className=\"group bg-white/60 dark:bg-slate-800/60 backdrop-blur-lg p-6 rounded-2xl shadow-sm border border-slate-200/50 dark:border-slate-700/50 hover:shadow-xl hover:-translate-y-1 transition-all duration-300\"\n >\n <div className=\"flex items-center justify-between mb-4\">\n <div\n className={`w-12 h-12 rounded-xl ${item.bgColor} flex items-center justify-center group-hover:scale-110 transition-transform duration-300 ${item.shadowColor} shadow-lg`}\n >\n <item.Icon\n className={`w-6 h-6 bg-gradient-to-r ${item.color}`}\n style={{ stroke: \"currentColor\" }}\n />\n </div>\n\n <div className={`flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-bold ${trendClassName}`}>\n <TrendIcon className=\"w-3 h-3\" />\n {item.change}\n </div>\n </div>\n\n <p className=\"text-sm font-medium text-slate-500 dark:text-slate-400 mb-1\">{item.label}</p>\n <p className=\"text-3xl font-bold text-slate-900 dark:text-white tracking-tight\">{item.value}</p>\n </div>\n );\n })}\n </div>\n );\n}\n",
|
|
29
|
+
"components/RecentActivity.tsx": "import { getRecentActivity } from \"../lib/stats.ts\";\n\nfunction ClockIcon({ className }: { className?: string }): React.JSX.Element {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <polyline points=\"12 6 12 12 16 14\" />\n </svg>\n );\n}\n\nfunction ActivityIcon({ className }: { className?: string }): React.JSX.Element {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <polyline points=\"22 12 18 12 15 21 9 3 6 12 2 12\" />\n </svg>\n );\n}\n\nconst ACTIVITY_COLORS: Record<string, string> = {\n login:\n \"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 border-blue-200 dark:border-blue-800\",\n purchase:\n \"bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400 border-emerald-200 dark:border-emerald-800\",\n signup:\n \"bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400 border-violet-200 dark:border-violet-800\",\n update:\n \"bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 border-amber-200 dark:border-amber-800\",\n default:\n \"bg-slate-100 text-slate-700 dark:bg-slate-700 dark:text-slate-400 border-slate-200 dark:border-slate-600\",\n};\n\nfunction getActivityColor(type: string): string {\n return ACTIVITY_COLORS[type.toLowerCase()] ?? ACTIVITY_COLORS.default;\n}\n\nexport async function RecentActivity({ userId }: { userId: string }): Promise<React.JSX.Element> {\n const activities = await getRecentActivity(userId);\n const hasActivities = activities.length > 0;\n\n return (\n <div className=\"bg-white/60 dark:bg-slate-800/60 backdrop-blur-lg rounded-2xl shadow-sm border border-slate-200/50 dark:border-slate-700/50 overflow-hidden\">\n <div className=\"p-6 border-b border-slate-200/50 dark:border-slate-700/50 flex items-center justify-between bg-white/50 dark:bg-slate-800/50\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-10 h-10 rounded-xl bg-indigo-500/10 flex items-center justify-center\">\n <ActivityIcon className=\"w-5 h-5 text-indigo-500\" />\n </div>\n <div>\n <h2 className=\"text-lg font-bold text-slate-900 dark:text-white\">Recent Activity</h2>\n <p className=\"text-sm text-slate-500 dark:text-slate-400\">Latest actions from your users</p>\n </div>\n </div>\n <a\n href=\"/dashboard/activity\"\n className=\"px-4 py-2 rounded-lg text-sm font-medium text-indigo-600 dark:text-indigo-400 hover:bg-indigo-50 dark:hover:bg-indigo-900/20 transition-colors\"\n >\n View all\n </a>\n </div>\n\n {hasActivities ? (\n <div className=\"divide-y divide-slate-200/50 dark:divide-slate-700/50\">\n {activities.map((activity) => (\n <div\n key={activity.id}\n className=\"p-6 hover:bg-white/50 dark:hover:bg-slate-700/30 transition-colors group\"\n >\n <div className=\"flex items-start gap-4\">\n <div className=\"flex-shrink-0 w-10 h-10 rounded-full bg-slate-100 dark:bg-slate-700/50 flex items-center justify-center group-hover:bg-indigo-50 dark:group-hover:bg-indigo-900/20 transition-colors\">\n <ClockIcon className=\"w-5 h-5 text-slate-400 group-hover:text-indigo-500 transition-colors\" />\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-slate-900 dark:text-white\">{activity.description}</p>\n <p className=\"text-xs text-slate-500 dark:text-slate-400 mt-1 flex items-center gap-1\">\n <ClockIcon className=\"w-3 h-3\" />\n {new Date(activity.timestamp).toLocaleString()}\n </p>\n </div>\n\n <span\n className={`flex-shrink-0 px-3 py-1 rounded-full text-xs font-bold border ${getActivityColor(\n activity.type,\n )}`}\n >\n {activity.type}\n </span>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <div className=\"p-12 text-center\">\n <div className=\"w-16 h-16 rounded-full bg-slate-100 dark:bg-slate-800 flex items-center justify-center mx-auto mb-4\">\n <ActivityIcon className=\"w-8 h-8 text-slate-300 dark:text-slate-600\" />\n </div>\n <p className=\"text-slate-500 dark:text-slate-400 font-medium\">No recent activity</p>\n </div>\n )}\n </div>\n );\n}\n",
|
|
30
|
+
"components/FeatureGrid.tsx": "const features = [\n {\n title: \"Authentication\",\n description: \"Secure user authentication with sessions and JWT tokens\",\n icon: (\n <svg className=\"w-6 h-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z\"\n />\n </svg>\n ),\n },\n {\n title: \"API Routes\",\n description: \"Full-featured API with middleware and validation\",\n icon: (\n <svg className=\"w-6 h-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z\" />\n </svg>\n ),\n },\n {\n title: \"Database Ready\",\n description: \"Easy to connect any database with our data layer\",\n icon: (\n <svg className=\"w-6 h-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125\"\n />\n </svg>\n ),\n },\n {\n title: \"Type Safe\",\n description: \"Full TypeScript support with runtime validation\",\n icon: (\n <svg className=\"w-6 h-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n ),\n },\n {\n title: \"Modern UI\",\n description: \"Beautiful components built with Tailwind CSS\",\n icon: (\n <svg className=\"w-6 h-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M9.53 16.122a3 3 0 00-5.78 1.128 2.25 2.25 0 01-2.4 2.245 4.5 4.5 0 008.4-2.245c0-.399-.078-.78-.22-1.128zm0 0a15.998 15.998 0 003.388-1.62m-5.043-.025a15.994 15.994 0 011.622-3.395m3.42 3.42a15.995 15.995 0 004.764-4.648l3.876-5.814a1.151 1.151 0 00-1.597-1.597L14.146 6.32a15.996 15.996 0 00-4.649 4.763m3.42 3.42a6.776 6.776 0 00-3.42-3.42\"\n />\n </svg>\n ),\n },\n {\n title: \"Production Ready\",\n description: \"Security, caching, and performance optimized\",\n icon: (\n <svg className=\"w-6 h-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0112 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5\"\n />\n </svg>\n ),\n },\n];\n\nexport function FeatureGrid(): React.JSX.Element {\n return (\n <section className=\"py-24 bg-neutral-50 dark:bg-neutral-900/50\">\n <div className=\"max-w-5xl mx-auto px-6\">\n <div className=\"text-center mb-16\">\n <h2 className=\"text-3xl font-bold text-neutral-900 dark:text-white mb-4\">Everything you need</h2>\n <p className=\"text-lg text-neutral-600 dark:text-neutral-400 max-w-xl mx-auto\">\n Start with a solid foundation and focus on your product.\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {features.map((feature) => (\n <div\n key={feature.title}\n className=\"bg-white dark:bg-neutral-800 rounded-2xl p-6 border border-neutral-200 dark:border-neutral-700\"\n >\n <div className=\"w-10 h-10 bg-blue-500/10 rounded-xl flex items-center justify-center text-blue-500 mb-4\">\n {feature.icon}\n </div>\n <h3 className=\"text-base font-semibold text-neutral-900 dark:text-white mb-2\">{feature.title}</h3>\n <p className=\"text-sm text-neutral-600 dark:text-neutral-400\">{feature.description}</p>\n </div>\n ))}\n </div>\n </div>\n </section>\n );\n}\n",
|
|
31
|
+
"components/Header.tsx": "'use client';\n\nimport { useAuth } from './AuthProvider.tsx';\n\nexport function Header(): JSX.Element {\n const { user, logout } = useAuth();\n\n return (\n <header className=\"sticky top-0 z-50 bg-white/80 dark:bg-neutral-900/80 backdrop-blur-lg border-b border-neutral-200 dark:border-neutral-800\">\n <nav className=\"max-w-5xl mx-auto px-6\">\n <div className=\"flex justify-between h-14 items-center\">\n <a href=\"/\" className=\"text-lg font-semibold text-neutral-900 dark:text-white\">\n My App\n </a>\n\n <div className=\"flex items-center gap-6\">\n {user ? (\n <>\n <a\n href=\"/dashboard\"\n className=\"text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n Dashboard\n </a>\n <span className=\"text-sm text-neutral-500\">{user.name}</span>\n <button\n type=\"button\"\n onClick={logout}\n className=\"text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n Sign out\n </button>\n </>\n ) : (\n <>\n <a\n href=\"/login\"\n className=\"text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n Sign in\n </a>\n <a\n href=\"/register\"\n className=\"text-sm px-4 py-2 bg-blue-500 text-white rounded-full hover:bg-blue-600 transition-colors\"\n >\n Get started\n </a>\n </>\n )}\n </div>\n </div>\n </nav>\n </header>\n );\n}\n",
|
|
32
|
+
"components/DashboardLayout.tsx": "'use client';\n\nimport * as React from 'react';\nimport { useAuth } from './AuthProvider.tsx';\n\nconst navigation = [\n {\n name: 'Overview',\n href: '/dashboard',\n icon: 'M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25',\n },\n {\n name: 'Users',\n href: '/dashboard/users',\n icon: 'M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z',\n },\n {\n name: 'Analytics',\n href: '/dashboard/analytics',\n icon: 'M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z',\n },\n {\n name: 'Settings',\n href: '/dashboard/settings',\n icon: 'M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z M15 12a3 3 0 11-6 0 3 3 0 016 0z',\n },\n];\n\nexport function DashboardLayout({ children }: { children: React.ReactNode }): React.ReactElement {\n const { user, logout } = useAuth();\n\n const userInitial = user?.name?.[0]?.toUpperCase() ?? 'U';\n\n return (\n <div className=\"min-h-screen bg-neutral-50 dark:bg-neutral-900\">\n <div className=\"flex h-screen\">\n <div className=\"w-64 bg-white dark:bg-neutral-800 border-r border-neutral-200 dark:border-neutral-700 flex flex-col\">\n <div className=\"p-6 border-b border-neutral-200 dark:border-neutral-700\">\n <a href=\"/\" className=\"text-lg font-semibold text-neutral-900 dark:text-white\">\n My App\n </a>\n </div>\n\n <nav className=\"flex-1 p-4 space-y-1\">\n {navigation.map((item) => (\n <a\n key={item.name}\n href={item.href}\n className=\"flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-xl text-neutral-600 dark:text-neutral-400 hover:bg-neutral-100 dark:hover:bg-neutral-700/50 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n <svg className=\"w-5 h-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d={item.icon} />\n </svg>\n {item.name}\n </a>\n ))}\n </nav>\n\n <div className=\"p-4 border-t border-neutral-200 dark:border-neutral-700\">\n <div className=\"flex items-center gap-3 p-3 rounded-xl bg-neutral-50 dark:bg-neutral-900\">\n <div className=\"w-9 h-9 rounded-full bg-blue-500 flex items-center justify-center text-white text-sm font-medium\">\n {userInitial}\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-neutral-900 dark:text-white truncate\">{user?.name}</p>\n <p className=\"text-xs text-neutral-500 truncate\">{user?.email}</p>\n </div>\n <button\n type=\"button\"\n onClick={logout}\n className=\"p-2 rounded-lg text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-700/50 transition-colors\"\n title=\"Sign out\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9\"\n />\n </svg>\n </button>\n </div>\n </div>\n </div>\n\n <div className=\"flex-1 overflow-y-auto\">\n <main className=\"p-8 max-w-5xl\">{children}</main>\n </div>\n </div>\n </div>\n );\n}\n",
|
|
33
|
+
"lib/auth.ts": "interface User {\n id: string;\n email: string;\n name: string;\n role: \"user\" | \"admin\";\n}\n\ninterface Session {\n token: string;\n userId: string;\n expiresAt: Date;\n}\n\nconst sessions = new Map<string, Session>();\nconst ONE_DAY_MS = 86_400_000;\n\nexport async function createSession(user: User): Promise<Session> {\n const token = crypto.randomUUID();\n const session: Session = {\n token,\n userId: user.id,\n expiresAt: new Date(Date.now() + ONE_DAY_MS),\n };\n\n sessions.set(token, session);\n return session;\n}\n\nexport async function verifySession(token: string): Promise<Session | null> {\n const session = sessions.get(token);\n if (!session) return null;\n\n if (session.expiresAt.getTime() < Date.now()) {\n sessions.delete(token);\n return null;\n }\n\n return session;\n}\n\nexport async function getSession(): Promise<{ user: User } | null> {\n return null;\n}\n\nexport async function deleteSession(token: string): Promise<void> {\n sessions.delete(token);\n}\n",
|
|
34
|
+
"lib/auth-client.ts": "export async function login(email: string, password: string): Promise<any> {\n const response = await fetch(\"/api/auth/login\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email, password }),\n });\n\n if (!response.ok) {\n const error = await response.json();\n throw new Error(error?.error ?? \"Login failed\");\n }\n\n return response.json();\n}\n\nexport async function logout(): Promise<void> {\n await fetch(\"/api/auth/logout\", { method: \"POST\" });\n window.location.href = \"/\";\n}\n\nexport async function register(data: {\n email: string;\n password: string;\n name: string;\n}): Promise<any> {\n const response = await fetch(\"/api/auth/register\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(data),\n });\n\n if (!response.ok) {\n const error = await response.json();\n throw new Error(error?.error ?? \"Registration failed\");\n }\n\n return response.json();\n}\n",
|
|
35
|
+
"lib/stats.ts": "interface Stats {\n totalUsers: number;\n activeToday: number;\n revenue: number;\n growth: number;\n}\n\ninterface RecentActivityItem {\n id: string;\n type: string;\n description: string;\n timestamp: Date;\n}\n\nexport async function getStats(userId: string): Promise<Stats> {\n return {\n totalUsers: 1234,\n activeToday: 89,\n revenue: 54321,\n growth: 12.5,\n };\n}\n\nexport async function getRecentActivity(userId: string): Promise<RecentActivityItem[]> {\n return [\n {\n id: \"1\",\n type: \"user_signup\",\n description: \"New user registered\",\n timestamp: new Date(Date.now() - 1000 * 60 * 5),\n },\n {\n id: \"2\",\n type: \"payment\",\n description: \"Payment received\",\n timestamp: new Date(Date.now() - 1000 * 60 * 30),\n },\n ];\n}\n",
|
|
36
|
+
"lib/users.ts": "import { nanoid } from \"nanoid\";\n\nlet hash: (password: string) => Promise<string>;\nlet compare: (password: string, hash: string) => Promise<boolean>;\n\n// @ts-ignore - Deno global\nif (typeof Deno !== \"undefined\") {\n const bcrypt = await import(\"https://deno.land/x/bcrypt@v0.4.1/mod.ts\");\n hash = bcrypt.hash;\n compare = bcrypt.compare;\n} else {\n // @ts-ignore\n const bcrypt = await import(\"@node-rs/bcrypt\");\n hash = bcrypt.hash;\n compare = bcrypt.compare;\n}\n\ninterface User {\n id: string;\n email: string;\n name: string;\n role: \"user\" | \"admin\";\n passwordHash: string;\n createdAt: Date;\n}\n\ntype PublicUser = Omit<User, \"passwordHash\">;\n\nfunction toPublicUser({ passwordHash: _passwordHash, ...user }: User): PublicUser {\n return user;\n}\n\n// In-memory storage (replace with database)\nconst users = new Map<string, User>();\n\n// Demo user\nconst demoUser: User = {\n id: \"demo-user\",\n email: \"demo@example.com\",\n name: \"Demo User\",\n role: \"user\",\n passwordHash: await hash(\"password\"),\n createdAt: new Date(),\n};\nusers.set(demoUser.id, demoUser);\n\nexport async function createUser(data: {\n email: string;\n name: string;\n password: string;\n role?: \"user\" | \"admin\";\n}): Promise<PublicUser> {\n const user: User = {\n id: nanoid(),\n email: data.email,\n name: data.name,\n role: data.role ?? \"user\",\n passwordHash: await hash(data.password),\n createdAt: new Date(),\n };\n\n users.set(user.id, user);\n return toPublicUser(user);\n}\n\nexport async function validatePassword(email: string, password: string): Promise<PublicUser | null> {\n const user = Array.from(users.values()).find((u) => u.email === email);\n if (!user) return null;\n\n const valid = await compare(password, user.passwordHash);\n if (!valid) return null;\n\n return toPublicUser(user);\n}\n\nexport async function getUsers(): Promise<PublicUser[]> {\n return Array.from(users.values()).map(toPublicUser);\n}\n\nexport async function getUser(id: string): Promise<PublicUser | null> {\n const user = users.get(id);\n return user ? toPublicUser(user) : null;\n}\n",
|
|
37
|
+
"app/dashboard/page.tsx": "import { redirect } from \"veryfront\";\nimport { getSession } from \"../../lib/auth.ts\";\nimport { DashboardLayout } from \"../../components/DashboardLayout.tsx\";\nimport { StatsGrid } from \"../../components/StatsGrid.tsx\";\nimport { RecentActivity } from \"../../components/RecentActivity.tsx\";\n\nexport default async function DashboardPage(): Promise<JSX.Element> {\n const session = await getSession();\n\n if (!session) {\n redirect(\"/login\");\n }\n\n const { id: userId, name } = session.user;\n\n return (\n <DashboardLayout>\n <div className=\"space-y-8\">\n <div>\n <h1 className=\"text-2xl font-bold text-neutral-900 dark:text-white\">Dashboard</h1>\n <p className=\"text-neutral-600 dark:text-neutral-400 mt-1\">Welcome back, {name}</p>\n </div>\n\n <StatsGrid userId={userId} />\n <RecentActivity userId={userId} />\n </div>\n </DashboardLayout>\n );\n}\n",
|
|
38
|
+
"app/api/auth/me/route.ts": "import { verifySession } from \"../../../../lib/auth.ts\";\nimport { getUser } from \"../../../../lib/users.ts\";\n\nexport async function GET(request: Request): Promise<Response> {\n try {\n const cookieHeader = request.headers.get(\"cookie\");\n if (!cookieHeader) {\n return Response.json({ error: \"Not authenticated\" }, { status: 401 });\n }\n\n const sessionToken = cookieHeader\n .split(\";\")\n .map((c) => c.trim())\n .find((c) => c.startsWith(\"session=\"))\n ?.split(\"=\")[1];\n\n if (!sessionToken) {\n return Response.json({ error: \"Not authenticated\" }, { status: 401 });\n }\n\n const session = await verifySession(sessionToken);\n if (!session) {\n return Response.json({ error: \"Invalid session\" }, { status: 401 });\n }\n\n const user = await getUser(session.userId);\n if (!user) {\n return Response.json({ error: \"User not found\" }, { status: 404 });\n }\n\n return Response.json({ user });\n } catch {\n return Response.json({ error: \"Internal server error\" }, { status: 500 });\n }\n}\n",
|
|
39
|
+
"app/api/auth/logout/route.ts": "import { deleteSession } from \"../../../../lib/auth.ts\";\n\nexport async function POST(request: Request): Promise<Response> {\n try {\n const cookieHeader = request.headers.get(\"cookie\") ?? \"\";\n const sessionToken = cookieHeader\n .split(\";\")\n .map((c) => c.trim())\n .find((c) => c.startsWith(\"session=\"))\n ?.split(\"=\")[1];\n\n if (sessionToken) {\n await deleteSession(sessionToken);\n }\n\n return Response.json(\n { success: true },\n {\n headers: {\n \"Set-Cookie\": \"session=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0\",\n },\n }\n );\n } catch {\n return Response.json({ error: \"Internal server error\" }, { status: 500 });\n }\n}\n",
|
|
40
|
+
"app/api/auth/login/route.ts": "import { z } from \"zod\";\nimport { getEnv } from \"veryfront/platform\";\nimport { createSession } from \"../../../../lib/auth.ts\";\nimport { validatePassword } from \"../../../../lib/users.ts\";\n\nconst loginSchema = z.object({\n email: z.string().email(),\n password: z.string().min(8),\n});\n\nexport async function POST(request: Request): Promise<Response> {\n try {\n const body = await request.json();\n const { email, password } = loginSchema.parse(body);\n\n const user = await validatePassword(email, password);\n if (!user) {\n return Response.json({ error: \"Invalid credentials\" }, { status: 401 });\n }\n\n const session = await createSession(user);\n\n const secureFlag = getEnv(\"NODE_ENV\") === \"production\" ? \"; Secure\" : \"\";\n\n return Response.json(\n { user, token: session.token },\n {\n headers: {\n \"Set-Cookie\": `session=${session.token}; Path=/; HttpOnly; SameSite=Strict${secureFlag}`,\n },\n }\n );\n } catch (error) {\n if (error instanceof z.ZodError) {\n return Response.json(\n { error: \"Invalid input\", details: error.errors },\n { status: 400 }\n );\n }\n\n return Response.json({ error: \"Internal server error\" }, { status: 500 });\n }\n}\n",
|
|
41
|
+
"app/api/auth/register/route.ts": "import { z } from \"zod\";\nimport { getEnv } from \"veryfront/platform\";\nimport { createSession } from \"../../../../lib/auth.ts\";\nimport { createUser } from \"../../../../lib/users.ts\";\n\nconst registerSchema = z.object({\n email: z.string().email(),\n name: z.string().min(2).max(100),\n password: z.string().min(8).max(100),\n});\n\nexport async function POST(request: Request): Promise<Response> {\n try {\n const body = await request.json();\n const { email, name, password } = registerSchema.parse(body);\n\n const user = await createUser({ email, name, password });\n const session = await createSession(user);\n\n const secureFlag = getEnv(\"NODE_ENV\") === \"production\" ? \"; Secure\" : \"\";\n\n return Response.json(\n { user, token: session.token },\n {\n status: 201,\n headers: {\n \"Set-Cookie\": `session=${session.token}; Path=/; HttpOnly; SameSite=Strict${secureFlag}`,\n },\n }\n );\n } catch (error) {\n if (error instanceof z.ZodError) {\n return Response.json(\n { error: \"Invalid input\", details: error.errors },\n { status: 400 }\n );\n }\n\n return Response.json({ error: \"Internal server error\" }, { status: 500 });\n }\n}\n",
|
|
42
|
+
"app/api/stats/route.ts": "import { requireAuth } from \"../../../middleware/auth.ts\";\nimport { getStats } from \"../../../lib/stats.ts\";\n\nexport async function GET(request: Request): Promise<Response> {\n const auth = await requireAuth(request);\n if (!auth.ok) return auth.response;\n\n const userId = new URL(request.url).searchParams.get(\"userId\");\n if (!userId) {\n return Response.json({ error: \"userId parameter required\" }, { status: 400 });\n }\n\n const stats = await getStats(userId);\n return Response.json({ stats });\n}\n",
|
|
43
|
+
"app/api/users/route.ts": "import { z } from \"zod\";\nimport { requireAuth } from \"../../../middleware/auth.ts\";\nimport { createUser, getUsers } from \"../../../lib/users.ts\";\n\nconst userSchema = z.object({\n name: z.string().min(1),\n email: z.string().email(),\n role: z.enum([\"user\", \"admin\"]).default(\"user\"),\n});\n\nexport async function GET(request: Request): Promise<Response> {\n const auth = await requireAuth(request);\n if (!auth.ok) return auth.response;\n\n const users = await getUsers();\n return Response.json({ users });\n}\n\nexport async function POST(request: Request): Promise<Response> {\n const auth = await requireAuth(request);\n if (!auth.ok) return auth.response;\n\n try {\n const data = userSchema.parse(await request.json());\n const user = await createUser(data);\n\n return Response.json({ user }, { status: 201 });\n } catch (error) {\n if (error instanceof z.ZodError) {\n return Response.json(\n { error: \"Invalid input\", details: error.errors },\n { status: 400 }\n );\n }\n\n return Response.json({ error: \"Internal server error\" }, { status: 500 });\n }\n}\n",
|
|
44
|
+
"app/login/page.tsx": "\"use client\";\n\nimport React, { useState } from \"react\";\nimport { login } from \"../../lib/auth-client.ts\";\n\nexport default function LoginPage(): React.JSX.Element {\n const [email, setEmail] = useState(\"\");\n const [password, setPassword] = useState(\"\");\n const [error, setError] = useState(\"\");\n const [loading, setLoading] = useState(false);\n const [showPassword, setShowPassword] = useState(false);\n\n async function handleSubmit(e: React.FormEvent): Promise<void> {\n e.preventDefault();\n setError(\"\");\n setLoading(true);\n\n try {\n await login(email, password);\n window.location.href = \"/dashboard\";\n } catch {\n setError(\"Invalid email or password\");\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div className=\"min-h-screen flex items-center justify-center bg-neutral-50 dark:bg-neutral-900 p-4\">\n <div className=\"w-full max-w-sm\">\n <div className=\"text-center mb-8\">\n <div className=\"w-12 h-12 mx-auto mb-4 rounded-2xl bg-blue-500 flex items-center justify-center\">\n <svg className=\"w-6 h-6 text-white\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z\"\n />\n </svg>\n </div>\n <h1 className=\"text-2xl font-bold text-neutral-900 dark:text-white\">Welcome back</h1>\n <p className=\"text-sm text-neutral-500 dark:text-neutral-400 mt-1\">Sign in to continue</p>\n </div>\n\n <div className=\"bg-white dark:bg-neutral-800 rounded-2xl border border-neutral-200 dark:border-neutral-700 p-6\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {error ? (\n <div className=\"bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-600 dark:text-red-400 px-4 py-3 rounded-xl text-sm\">\n {error}\n </div>\n ) : null}\n\n <div>\n <label htmlFor=\"email\" className=\"block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1.5\">\n Email\n </label>\n <input\n id=\"email\"\n type=\"email\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n placeholder=\"you@example.com\"\n className=\"w-full px-4 py-2.5 bg-neutral-50 dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 rounded-xl text-neutral-900 dark:text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors\"\n />\n </div>\n\n <div>\n <div className=\"flex items-center justify-between mb-1.5\">\n <label htmlFor=\"password\" className=\"block text-sm font-medium text-neutral-700 dark:text-neutral-300\">\n Password\n </label>\n <a href=\"/forgot-password\" className=\"text-sm text-blue-500 hover:text-blue-600\">\n Forgot?\n </a>\n </div>\n\n <div className=\"relative\">\n <input\n id=\"password\"\n type={showPassword ? \"text\" : \"password\"}\n required\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n placeholder=\"Enter password\"\n className=\"w-full px-4 py-2.5 pr-10 bg-neutral-50 dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 rounded-xl text-neutral-900 dark:text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors\"\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword((v) => !v)}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-300\"\n >\n {showPassword ? (\n <svg className=\"w-5 h-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88\"\n />\n </svg>\n ) : (\n <svg className=\"w-5 h-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z\"\n />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n </svg>\n )}\n </button>\n </div>\n </div>\n\n <button\n type=\"submit\"\n disabled={loading}\n className=\"w-full py-2.5 px-4 bg-blue-500 text-white font-medium rounded-xl hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500/30 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {loading ? \"Signing in...\" : \"Sign in\"}\n </button>\n </form>\n\n <div className=\"relative my-6\">\n <div className=\"absolute inset-0 flex items-center\">\n <div className=\"w-full border-t border-neutral-200 dark:border-neutral-700\" />\n </div>\n <div className=\"relative flex justify-center text-sm\">\n <span className=\"px-3 bg-white dark:bg-neutral-800 text-neutral-500\">or</span>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <button className=\"flex items-center justify-center gap-2 px-4 py-2.5 border border-neutral-200 dark:border-neutral-700 rounded-xl text-sm text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-colors\">\n <svg className=\"w-4 h-4\" viewBox=\"0 0 24 24\">\n <path\n fill=\"currentColor\"\n d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z\"\n />\n <path\n fill=\"currentColor\"\n d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"\n />\n <path\n fill=\"currentColor\"\n d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"\n />\n <path\n fill=\"currentColor\"\n d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"\n />\n </svg>\n Google\n </button>\n\n <button className=\"flex items-center justify-center gap-2 px-4 py-2.5 border border-neutral-200 dark:border-neutral-700 rounded-xl text-sm text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-colors\">\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z\" />\n </svg>\n GitHub\n </button>\n </div>\n </div>\n\n <p className=\"mt-6 text-center text-sm text-neutral-600 dark:text-neutral-400\">\n Don't have an account?{\" \"}\n <a href=\"/register\" className=\"text-blue-500 hover:text-blue-600 font-medium\">\n Sign up\n </a>\n </p>\n </div>\n </div>\n );\n}\n",
|
|
45
|
+
"app/layout.tsx": "import * as React from \"react\";\nimport { AuthProvider } from \"../components/AuthProvider.tsx\";\nimport { Toaster } from \"../components/Toaster.tsx\";\n\nexport const metadata = {\n title: \"My App\",\n description: \"A full-stack app built with Veryfront\",\n};\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.JSX.Element {\n return (\n <AuthProvider>\n <div className=\"min-h-screen bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100 antialiased\">\n {children}\n <Toaster />\n </div>\n </AuthProvider>\n );\n}\n",
|
|
46
|
+
"app/page.tsx": "import { FeatureGrid } from \"../components/FeatureGrid.tsx\";\nimport { Header } from \"../components/Header.tsx\";\nimport { HeroSection } from \"../components/HeroSection.tsx\";\n\nexport default function HomePage(): JSX.Element {\n return (\n <>\n <Header />\n <main>\n <HeroSection />\n <FeatureGrid />\n </main>\n </>\n );\n}\n",
|
|
47
|
+
"public/robots.txt": "User-agent: *\nAllow: /\n\nSitemap: /sitemap.xml",
|
|
48
|
+
"layout.tsx": "export default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.ReactNode {\n return <div className=\"h-full bg-slate-50 dark:bg-slate-900\">{children}</div>;\n}\n",
|
|
49
|
+
".env.example": "# Environment variables\nDATABASE_URL=postgresql://user:password@localhost:5432/myapp\nREDIS_URL=redis://localhost:6379\nJWT_SECRET=your-secret-key-here\n\n# OAuth providers\nGITHUB_CLIENT_ID=\nGITHUB_CLIENT_SECRET=\nGOOGLE_CLIENT_ID=\nGOOGLE_CLIENT_SECRET=\n\n# Email service\nSMTP_HOST=smtp.example.com\nSMTP_PORT=587\nSMTP_USER=\nSMTP_PASS=",
|
|
50
|
+
"page.tsx": "'use client'\n\nimport { Chat } from 'veryfront/components/ai'\nimport { useChat } from 'veryfront/agent/react'\n\nexport default function ChatPage(): JSX.Element {\n const chat = useChat({ api: '/api/chat' })\n\n return (\n <div className=\"flex flex-col h-screen bg-white dark:bg-neutral-900\">\n <header className=\"sticky top-0 z-10 flex-shrink-0 border-b border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900\">\n <div className=\"flex items-center justify-between px-4 py-3\">\n <h1 className=\"font-medium text-neutral-900 dark:text-white\">AI Assistant</h1>\n <a\n href=\"/setup\"\n className=\"text-sm text-neutral-500 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200\"\n >\n Setup\n </a>\n </div>\n </header>\n\n <Chat {...chat} className=\"flex-1 min-h-0\" placeholder=\"Message\" />\n </div>\n )\n}\n"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"docs": {
|
|
54
|
+
"files": {
|
|
55
|
+
"components/Sidebar.tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nconst navigation = [\n {\n title: \"Getting Started\",\n items: [\n { title: \"Introduction\", href: \"/\" },\n { title: \"Installation\", href: \"/docs/getting-started\" },\n { title: \"Quick Start\", href: \"/docs/getting-started#quick-start\" },\n ],\n },\n {\n title: \"Core Concepts\",\n items: [\n { title: \"Overview\", href: \"/docs/core-concepts\" },\n { title: \"Architecture\", href: \"/docs/core-concepts#architecture\" },\n { title: \"Data Flow\", href: \"/docs/core-concepts#data-flow\" },\n ],\n },\n {\n title: \"API Reference\",\n items: [\n { title: \"Core API\", href: \"/docs/api\" },\n { title: \"Components\", href: \"/docs/api#components\" },\n { title: \"Hooks\", href: \"/docs/api#hooks\" },\n ],\n },\n];\n\nexport function Sidebar(): React.JSX.Element {\n const [pathname, setPathname] = React.useState<string>(\"/\");\n\n React.useEffect(() => {\n setPathname(window.location.pathname);\n }, []);\n\n return (\n <aside className=\"w-56 shrink-0 border-r border-neutral-200 dark:border-neutral-800 min-h-[calc(100vh-3.5rem)]\">\n <nav className=\"p-4 space-y-6 sticky top-14\">\n {navigation.map((section) => (\n <div key={section.title}>\n <h3 className=\"text-xs font-semibold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider mb-2 px-3\">\n {section.title}\n </h3>\n <ul className=\"space-y-0.5\">\n {section.items.map((item) => {\n const isActive = pathname === item.href;\n\n return (\n <li key={item.href}>\n <a\n href={item.href}\n className={[\n \"block px-3 py-1.5 text-sm rounded-lg transition-colors\",\n isActive\n ? \"bg-blue-500/10 text-blue-500 font-medium\"\n : \"text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800\",\n ].join(\" \")}\n >\n {item.title}\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n ))}\n </nav>\n </aside>\n );\n}\n",
|
|
56
|
+
"components/CodeBlock.tsx": "'use client';\n\nimport { useState } from 'react';\n\ninterface CodeBlockProps {\n children: string;\n language?: string;\n filename?: string;\n}\n\nexport function CodeBlock({\n children,\n language = 'typescript',\n filename,\n}: CodeBlockProps): JSX.Element {\n const [copied, setCopied] = useState(false);\n\n async function handleCopy(): Promise<void> {\n await navigator.clipboard.writeText(children);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }\n\n const preClassName = `bg-gray-800 text-gray-100 p-4 rounded-lg overflow-x-auto${\n filename ? ' rounded-t-none' : ''\n }`;\n\n return (\n <div className=\"relative group\">\n {filename ? (\n <div className=\"bg-gray-700 text-gray-300 px-4 py-2 text-sm rounded-t-lg\">\n {filename}\n </div>\n ) : null}\n\n <pre className={preClassName}>\n <code className={`language-${language}`}>{children}</code>\n </pre>\n\n <button\n onClick={handleCopy}\n className=\"absolute top-2 right-2 px-3 py-1 bg-gray-700 text-gray-300 rounded text-sm opacity-0 group-hover:opacity-100 transition-opacity\"\n >\n {copied ? 'Copied!' : 'Copy'}\n </button>\n </div>\n );\n}\n",
|
|
57
|
+
"components/Header.tsx": "'use client';\n\nimport { useState } from 'react';\n\nexport function Header(): React.JSX.Element {\n const [searchQuery, setSearchQuery] = useState('');\n\n function handleSearchChange(e: React.ChangeEvent<HTMLInputElement>): void {\n setSearchQuery(e.target.value);\n }\n\n return (\n <header className=\"sticky top-0 z-50 bg-white/80 dark:bg-neutral-900/80 backdrop-blur-lg border-b border-neutral-200 dark:border-neutral-800\">\n <div className=\"px-6 h-14 flex items-center justify-between\">\n <div className=\"flex items-center gap-8\">\n <a href=\"/\" className=\"text-lg font-semibold text-neutral-900 dark:text-white\">\n Docs\n </a>\n <nav className=\"flex gap-6\">\n <a\n href=\"/docs\"\n className=\"text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n Docs\n </a>\n <a\n href=\"/docs/api\"\n className=\"text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n API\n </a>\n <a\n href=\"/examples\"\n className=\"text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n Examples\n </a>\n </nav>\n </div>\n\n <div className=\"flex items-center gap-4\">\n <div className=\"relative\">\n <svg\n className=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-neutral-400\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={1.5}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z\"\n />\n </svg>\n <input\n type=\"search\"\n placeholder=\"Search...\"\n value={searchQuery}\n onChange={handleSearchChange}\n className=\"w-48 pl-9 pr-4 py-2 bg-neutral-100 dark:bg-neutral-800 border-0 rounded-lg text-sm text-neutral-900 dark:text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-blue-500/30\"\n />\n </div>\n\n <a\n href=\"https://github.com\"\n className=\"text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z\" />\n </svg>\n </a>\n </div>\n </div>\n </header>\n );\n}\n",
|
|
58
|
+
"app/page.mdx": "# Documentation\n\nThis is the default docs landing page. Replace it with content for your product.\n\n## Start Here\n\n- Explain what your product does and who it is for.\n- Link to the top 3 tasks users come here to do.\n\n## Suggested Sections\n\n- Getting Started\n- Core Concepts\n- API Reference\n- Guides\n- Examples\n\n## Support\n\n- Link to your support channels\n",
|
|
59
|
+
"app/docs/api/page.mdx": "# API Reference\n\nComplete API documentation for all available functions and components.\n\n## Core API\n\n### `createApp()`\n\nCreates a new application instance.\n\n```typescript\nimport { createApp } from '@example/core';\n\nconst app = createApp({\n name: 'My App',\n version: '1.0.0',\n});\n```\n\n**Parameters:**\n- `config` - Application configuration object\n - `name` (string) - Application name\n - `version` (string) - Application version\n - `plugins?` (Plugin[]) - Optional plugins\n\n**Returns:** `Application` instance\n\n### `defineRoute()`\n\nDefines a new route handler.\n\n```typescript\nexport const route = defineRoute({\n path: '/api/users/:id',\n method: 'GET',\n handler: async (req, params) => {\n const user = await getUser(params.id);\n return Response.json(user);\n },\n});\n```\n\n## Components\n\n### `<Layout>`\n\nBase layout component for pages.\n\n```tsx\nimport { Layout } from '@example/ui';\n\nexport default function Page() {\n return (\n <Layout title=\"My Page\">\n <h1>Content</h1>\n </Layout>\n );\n}\n```\n\n**Props:**\n- `title` (string) - Page title\n- `children` (ReactNode) - Page content\n- `sidebar?` (boolean) - Show sidebar\n\n### `<Button>`\n\nStyled button component.\n\n```tsx\n<Button\n variant=\"primary\"\n size=\"lg\"\n onClick={handleClick}\n>\n Click me\n</Button>\n```\n\n**Props:**\n- `variant` - \"primary\" | \"secondary\" | \"danger\"\n- `size` - \"sm\" | \"md\" | \"lg\"\n- `disabled?` (boolean)\n- `onClick?` (function)\n\n## Hooks\n\n### `useData()`\n\nFetches and caches data.\n\n```typescript\nconst { data, error, loading } = useData('/api/users');\n```\n\n### `useAuth()`\n\nAuthentication hook.\n\n```typescript\nconst { user, login, logout } = useAuth();\n```",
|
|
60
|
+
"app/docs/core-concepts/page.mdx": "# Core Concepts\n\nUnderstanding these core concepts will help you build better applications.\n\n## Architecture Overview\n\nOur platform is built on these key principles:\n\n### 1. Component-Based\n\nEverything is a component that can be composed together:\n\n```typescript\nexport function MyComponent({ name }: { name: string }) {\n return <div>Hello, {name}!</div>;\n}\n```\n\n### 2. File-System Routing\n\nRoutes are automatically generated based on your file structure:\n\n- `app/page.tsx` → `/`\n- `app/about/page.tsx` → `/about`\n- `app/blog/[slug]/page.tsx` → `/blog/:slug`\n\n### 3. Server-First\n\nBy default, components run on the server for better performance:\n\n```typescript\n// This runs on the server\nexport default async function Page() {\n const data = await fetchData();\n return <div>{data}</div>;\n}\n```\n\n### 4. Progressive Enhancement\n\nAdd client interactivity only where needed:\n\n```typescript\n'use client';\n\nexport function Counter() {\n const [count, setCount] = useState(0);\n return (\n <button onClick={() => setCount(count + 1)}>\n Count: {count}\n </button>\n );\n}\n```\n\n## Data Flow\n\nUnderstanding how data flows through your application:\n\n1. **Request** - User navigates to a route\n2. **Routing** - System matches the URL to a page\n3. **Data Fetching** - Page fetches required data\n4. **Rendering** - Server renders the HTML\n5. **Hydration** - Client adds interactivity\n\n## State Management\n\nManage state at different levels:\n\n- **Component State** - Local to a component\n- **Context** - Shared across components\n- **Server State** - Fetched from APIs\n- **URL State** - Stored in query parameters\n\n## Performance\n\nBuilt-in optimizations include:\n\n- Automatic code splitting\n- Resource prefetching\n- Image optimization\n- Caching strategies",
|
|
61
|
+
"app/docs/getting-started/page.mdx": "# Getting Started\n\nThis guide will help you get started with our platform in just a few minutes.\n\n## Prerequisites\n\nBefore you begin, make sure you have:\n\n- Deno 1.40 or later installed\n- A text editor (we recommend VS Code)\n- Basic knowledge of JavaScript/TypeScript\n\n## Installation\n\n### Using Deno\n\n```bash\ndeno install -A -n myapp https://example.com/cli.ts\n```\n\n### From Source\n\n```bash\ngit clone https://github.com/example/myapp\ncd myapp\ndeno task install\n```\n\n## Creating Your First Project\n\nOnce installed, create a new project:\n\n```bash\nmyapp init my-first-project\ncd my-first-project\n```\n\nThis creates a new project with the following structure:\n\n```\nmy-first-project/\n├── src/\n│ ├── main.ts\n│ └── utils.ts\n├── tests/\n│ └── main_test.ts\n├── deno.json\n└── README.md\n```\n\n## Running Your Project\n\nStart the development server:\n\n```bash\nmyapp dev\n```\n\nYour application will be available at `http://localhost:3000`.\n\n## Next Steps\n\nNow that you have a project running, explore:\n\n- [Core Concepts](/docs/core-concepts) - Understand the architecture\n- [Configuration](/docs/configuration) - Customize your setup\n- [Deployment](/docs/deployment) - Deploy to production",
|
|
62
|
+
"app/layout.tsx": "import { Sidebar } from \"../components/Sidebar.tsx\";\nimport { Header } from \"../components/Header.tsx\";\n\nexport const metadata = {\n title: \"My Docs\",\n description: \"Documentation built with Veryfront\",\n};\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.JSX.Element {\n return (\n <div className=\"min-h-screen bg-white dark:bg-neutral-900\">\n <Header />\n <div className=\"flex\">\n <Sidebar />\n <main className=\"flex-1 px-8 py-8 max-w-3xl\">\n <article className=\"prose prose-neutral dark:prose-invert max-w-none\">\n {children}\n </article>\n </main>\n </div>\n </div>\n );\n}\n",
|
|
63
|
+
"public/robots.txt": "User-agent: *\nAllow: /\n\nSitemap: /sitemap.xml",
|
|
64
|
+
"styles/globals.css": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n/* Documentation styles */\n.prose h1 {\n scroll-margin-top: 5rem;\n}\n\n.prose h2,\n.prose h3 {\n scroll-margin-top: 4rem;\n}\n\n/* Code highlighting */\n.prose pre {\n background-color: #1e293b;\n color: #e2e8f0;\n padding: 1rem;\n overflow-x: auto;\n}\n\n.prose code {\n background-color: #f3f4f6;\n padding: 0.125rem 0.375rem;\n border-radius: 0.25rem;\n font-size: 0.875em;\n color: #111827;\n}\n\n.prose pre code {\n background-color: transparent;\n padding: 0;\n color: inherit;\n}\n\n/* Sidebar active state */\n.sidebar-link-active {\n background-color: #3b82f6;\n color: white;\n}\n\n/* Search highlight */\n.search-highlight {\n background-color: #fef3c7;\n padding: 0.125rem 0.25rem;\n border-radius: 0.125rem;\n}"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"blog": {
|
|
68
|
+
"files": {
|
|
69
|
+
"components/BlogPostList.tsx": "import { formatDate } from \"../lib/utils.ts\";\n\ninterface Post {\n slug: string;\n title: string;\n date: string;\n excerpt?: string;\n tags?: string[];\n}\n\nexport function BlogPostList({ posts }: { posts: Post[] }): JSX.Element {\n return (\n <div className=\"space-y-10\">\n {posts.map((post) => (\n <article key={post.slug}>\n <a href={`/blog/${post.slug}`} className=\"group block\">\n <time className=\"text-sm text-neutral-500 dark:text-neutral-400\">\n {formatDate(post.date)}\n </time>\n <h2 className=\"text-xl font-semibold text-neutral-900 dark:text-white mt-1 group-hover:text-blue-500 transition-colors\">\n {post.title}\n </h2>\n {post.excerpt ? (\n <p className=\"text-neutral-600 dark:text-neutral-400 mt-2 line-clamp-2\">\n {post.excerpt}\n </p>\n ) : null}\n </a>\n\n {post.tags?.length ? (\n <div className=\"flex gap-2 mt-3\">\n {post.tags.map((tag) => (\n <span\n key={tag}\n className=\"px-2 py-1 bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-400 rounded-md text-xs\"\n >\n {tag}\n </span>\n ))}\n </div>\n ) : null}\n </article>\n ))}\n </div>\n );\n}\n",
|
|
70
|
+
"components/MDXContent.tsx": "'use client';\n\nimport * as React from 'react';\nimport { MDXProvider } from '@mdx-js/react';\n\nconst components = {\n pre: ({ children, ...props }: React.ComponentProps<'pre'>) => (\n <pre {...props} className=\"bg-gray-100 p-4 rounded-lg overflow-x-auto\">\n {children}\n </pre>\n ),\n code: ({ children, ...props }: React.ComponentProps<'code'>) => (\n <code {...props} className=\"bg-gray-100 px-1 py-0.5 rounded text-sm\">\n {children}\n </code>\n ),\n};\n\nexport function MDXContent({ content }: { content: React.ReactNode }): React.JSX.Element {\n return <MDXProvider components={components}>{content}</MDXProvider>;\n}\n",
|
|
71
|
+
"lib/posts.ts": "import { parse as parseYaml } from \"yaml\";\nimport { join } from \"veryfront/platform/path\";\nimport { cwd, createFileSystem } from \"veryfront/platform\";\n\ninterface PostMeta {\n title: string;\n date: string;\n author?: string;\n tags?: string[];\n excerpt?: string;\n}\n\nexport interface Post extends PostMeta {\n slug: string;\n content: string;\n}\n\nconst fs = createFileSystem();\n\nfunction getPostsDir(): string {\n return join(cwd(), \"content\", \"posts\");\n}\n\nfunction extractFrontmatter(content: string): { attrs: PostMeta; body: string } {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/);\n if (!match) {\n return {\n attrs: { title: \"Untitled\", date: new Date().toISOString() },\n body: content,\n };\n }\n\n const attrs = parseYaml(match[1]) as PostMeta;\n return { attrs, body: match[2].trim() };\n}\n\nexport async function getPosts(): Promise<Post[]> {\n const posts: Post[] = [];\n const postsDir = getPostsDir();\n\n try {\n for await (const entry of fs.readDir(postsDir)) {\n if (!entry.isFile || !entry.name.endsWith(\".mdx\")) continue;\n\n const slug = entry.name.replace(/\\.mdx$/, \"\");\n const content = await fs.readTextFile(join(postsDir, entry.name));\n const { attrs, body } = extractFrontmatter(content);\n\n posts.push({ slug, content: body, ...attrs });\n }\n } catch (error) {\n console.error(\"Error reading posts:\", error);\n }\n\n return posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());\n}\n\nexport async function getPost(slug: string): Promise<Post | null> {\n const postsDir = getPostsDir();\n\n try {\n const content = await fs.readTextFile(join(postsDir, `${slug}.mdx`));\n const { attrs, body } = extractFrontmatter(content);\n return { slug, content: body, ...attrs };\n } catch {\n return null;\n }\n}\n",
|
|
72
|
+
"lib/utils.ts": "export function formatDate(date: string): string {\n return new Date(date).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n}\n\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}",
|
|
73
|
+
"app/archive/page.tsx": "import { getPosts } from \"../../lib/posts.ts\";\nimport { formatDate } from \"../../lib/utils.ts\";\n\nexport default async function Archive(): Promise<JSX.Element> {\n const posts = await getPosts();\n\n const postsByYear = posts.reduce<Record<number, typeof posts>>((acc, post) => {\n const year = new Date(post.date).getFullYear();\n (acc[year] ??= []).push(post);\n return acc;\n }, {});\n\n const years = Object.keys(postsByYear)\n .map(Number)\n .sort((a, b) => b - a);\n\n return (\n <div>\n <h1 className=\"text-4xl font-bold mb-8\">Archive</h1>\n {years.map((year) => (\n <div key={year} className=\"mb-8\">\n <h2 className=\"text-2xl font-semibold mb-4\">{year}</h2>\n <ul className=\"space-y-2\">\n {postsByYear[year]?.map((post) => (\n <li key={post.slug}>\n <a\n href={`/blog/${post.slug}`}\n className=\"text-blue-600 hover:underline\"\n >\n {post.title}\n </a>\n <span className=\"text-gray-600 ml-2\">\n {formatDate(post.date)}\n </span>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n );\n}\n",
|
|
74
|
+
"app/layout.tsx": "import type { ReactNode } from \"react\";\n\nexport const metadata = {\n title: \"My Blog\",\n description: \"A blog built with Veryfront\",\n};\n\nexport default function RootLayout({ children }: { children: ReactNode }): ReactNode {\n return (\n <div className=\"min-h-screen bg-white dark:bg-neutral-900\">\n <header className=\"sticky top-0 z-50 bg-white/80 dark:bg-neutral-900/80 backdrop-blur-lg border-b border-neutral-200 dark:border-neutral-800\">\n <nav className=\"max-w-2xl mx-auto px-6\">\n <div className=\"flex justify-between h-14 items-center\">\n <a href=\"/\" className=\"text-lg font-semibold text-neutral-900 dark:text-white\">\n My Blog\n </a>\n <div className=\"flex gap-6\">\n <a\n href=\"/\"\n className=\"text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n Home\n </a>\n <a\n href=\"/about\"\n className=\"text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n About\n </a>\n <a\n href=\"/archive\"\n className=\"text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n Archive\n </a>\n </div>\n </div>\n </nav>\n </header>\n\n <main className=\"max-w-2xl mx-auto px-6 py-12\">{children}</main>\n\n <footer className=\"border-t border-neutral-200 dark:border-neutral-800 mt-16\">\n <div className=\"max-w-2xl mx-auto px-6 py-8 text-center text-sm text-neutral-500 dark:text-neutral-400\">\n Built with Veryfront\n </div>\n </footer>\n </div>\n );\n}\n",
|
|
75
|
+
"app/blog/[slug]/page.tsx": "import { MDX } from \"veryfront/mdx\";\nimport { getPost, getPosts } from \"../../../lib/posts.ts\";\nimport { formatDate } from \"../../../lib/utils.ts\";\n\nexport default async function BlogPost({\n params,\n}: {\n params: { slug: string };\n}): Promise<JSX.Element> {\n const post = await getPost(params.slug);\n\n if (!post) return <div>Post not found</div>;\n\n return (\n <article className=\"prose lg:prose-lg mx-auto\">\n <header className=\"mb-8\">\n <h1 className=\"mb-2\">{post.title}</h1>\n <div className=\"text-gray-600\">\n <time>{formatDate(post.date)}</time>\n {post.author && <span> · By {post.author}</span>}\n </div>\n\n {post.tags?.length ? (\n <div className=\"flex gap-2 mt-4\">\n {post.tags.map((tag) => (\n <span\n key={tag}\n className=\"px-2 py-1 bg-blue-100 text-blue-700 rounded text-sm\"\n >\n {tag}\n </span>\n ))}\n </div>\n ) : null}\n </header>\n\n <MDX source={post.content} />\n </article>\n );\n}\n\nexport async function generateStaticParams(): Promise<Array<{ slug: string }>> {\n const posts = await getPosts();\n return posts.map((post) => ({ slug: post.slug }));\n}\n",
|
|
76
|
+
"app/about/page.mdx": "# About\n\nWelcome to my blog! I write about technology, programming, and life.\n\n## Contact\n\nYou can reach me at:\n- Email: hello@example.com\n- Twitter: @yourhandle\n- GitHub: @yourusername\n\n## About This Site\n\nThis blog is built with [Veryfront](https://github.com/veryfront/veryfront), a Deno-first React framework with excellent MDX support.",
|
|
77
|
+
"app/page.tsx": "import { BlogPostList } from \"../components/BlogPostList.tsx\";\nimport { getPosts } from \"../lib/posts.ts\";\n\nexport default async function HomePage(): Promise<JSX.Element> {\n const posts = await getPosts();\n\n return (\n <div>\n <h1 className=\"mb-8 text-3xl font-bold text-neutral-900 dark:text-white\">Latest Posts</h1>\n <BlogPostList posts={posts} />\n </div>\n );\n}\n",
|
|
78
|
+
"public/robots.txt": "User-agent: *\nAllow: /\n\nSitemap: /sitemap.xml",
|
|
79
|
+
"content/posts/hello-world.mdx": "---\ntitle: Hello World\ndate: 2024-01-01\nauthor: Your Name\ntags: [intro, meta]\nexcerpt: Welcome to my new blog built with Veryfront!\n---\n\nWelcome to my new blog! This is my first post built with Veryfront.\n\n## Why Veryfront?\n\nI chose Veryfront because:\n\n1. **Deno-first** - No node_modules, secure by default\n2. **Great MDX support** - Write content with React components\n3. **Fast** - Built on web standards\n4. **Simple** - Easy to understand and customize\n\n## What's Next?\n\nI'll be writing about:\n\n- Web development tips and tricks\n- My experiences with Deno and JavaScript\n- Building applications with React Server Components\n- And much more!\n\nStay tuned for more posts!\n",
|
|
80
|
+
"content/posts/markdown-showcase.mdx": "---\ntitle: Markdown Showcase\ndate: 2024-01-02\nauthor: Your Name\ntags: [markdown, demo]\nexcerpt: A demonstration of all the Markdown features supported in Veryfront\n---\n\nThis post showcases all the Markdown and MDX features available in Veryfront.\n\n## Headers\n\n### H3 Header\n#### H4 Header\n##### H5 Header\n###### H6 Header\n\n## Emphasis\n\n*This text is italicized*\n**This text is bold**\n***This text is bold and italicized***\n~~This text is struck through~~\n\n## Lists\n\n### Unordered List\n- First item\n- Second item\n - Nested item\n - Another nested item\n- Third item\n\n### Ordered List\n1. First step\n2. Second step\n 1. Sub-step A\n 2. Sub-step B\n3. Third step\n\n## Links and Images\n\n[Visit Veryfront](https://github.com/veryfront/veryfront)\n\n\n\n## Code\n\nInline code: `const greeting = \"Hello, World!\"`\n\n```javascript\n// Code block with syntax highlighting\nfunction fibonacci(n) {\n if (n <= 1) return n;\n return fibonacci(n - 1) + fibonacci(n - 2);\n}\n\nconsole.log(fibonacci(10)); // 55\n```\n\n```tsx\n// React component\nexport function Counter() {\n const [count, setCount] = useState(0);\n\n return (\n <button onClick={() => setCount(count + 1)}>\n Count: {count}\n </button>\n );\n}\n```\n\n## Blockquotes\n\n> \"The best way to predict the future is to invent it.\"\n> — Alan Kay\n\n## Tables\n\n| Feature | Supported | Notes |\n|---------|-----------|--------|\n| MDX | ✅ | Full support |\n| RSC | ✅ | React Server Components |\n| HMR | ✅ | Hot Module Replacement |\n| TypeScript | ✅ | Built-in support |\n\n## Task Lists\n\n- [x] Set up Veryfront\n- [x] Create first blog post\n- [ ] Customize theme\n- [ ] Add comments system\n\n## Horizontal Rule\n\n---\n\n## MDX Components\n\nYou can also embed React components directly in your markdown:\n\n<button className=\"px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600\">\n Click me!\n</button>",
|
|
81
|
+
"styles/globals.css": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n/* Custom styles for blog */\n.prose pre {\n background-color: #1e293b;\n color: #e2e8f0;\n}\n\n.prose code {\n background-color: #f3f4f6;\n padding: 0.125rem 0.25rem;\n border-radius: 0.25rem;\n font-size: 0.875em;\n}\n\n.prose pre code {\n background-color: transparent;\n padding: 0;\n}"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"integration:jira": {
|
|
85
|
+
"files": {
|
|
86
|
+
"lib/jira-client.ts": "import { getAccessToken, getCloudId } from \"./token-store.ts\";\n\nconst JIRA_API_VERSION = \"3\";\n\ninterface JiraResponse<T> {\n expand?: string;\n startAt?: number;\n maxResults?: number;\n total?: number;\n issues?: T[];\n values?: T[];\n}\n\nexport interface JiraIssue {\n id: string;\n key: string;\n self: string;\n fields: {\n summary: string;\n description?:\n | {\n type: string;\n content: unknown[];\n }\n | string;\n status: {\n name: string;\n statusCategory: {\n key: string;\n name: string;\n };\n };\n issuetype: {\n id: string;\n name: string;\n iconUrl: string;\n };\n priority?: {\n name: string;\n iconUrl: string;\n };\n assignee?: {\n displayName: string;\n emailAddress: string;\n accountId: string;\n };\n reporter?: {\n displayName: string;\n emailAddress: string;\n accountId: string;\n };\n created: string;\n updated: string;\n project: {\n id: string;\n key: string;\n name: string;\n };\n labels?: string[];\n [key: string]: unknown;\n };\n}\n\nexport interface JiraProject {\n id: string;\n key: string;\n name: string;\n projectTypeKey: string;\n self: string;\n avatarUrls?: Record<string, string>;\n lead?: {\n displayName: string;\n accountId: string;\n };\n}\n\nexport interface JiraIssueType {\n id: string;\n name: string;\n description: string;\n iconUrl: string;\n subtask: boolean;\n}\n\nexport interface JiraTransition {\n id: string;\n name: string;\n to: {\n id: string;\n name: string;\n };\n}\n\nfunction buildAdfDescription(text: string): Record<string, unknown> {\n return {\n type: \"doc\",\n version: 1,\n content: [\n {\n type: \"paragraph\",\n content: [\n {\n type: \"text\",\n text,\n },\n ],\n },\n ],\n };\n}\n\nasync function jiraFetch<T>(\n endpoint: string,\n options: RequestInit = {},\n): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Jira. Please connect your account.\");\n }\n\n const cloudId = await getCloudId();\n if (!cloudId) {\n throw new Error(\"Jira cloud ID not found. Please reconnect your account.\");\n }\n\n const baseUrl = `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/${JIRA_API_VERSION}`;\n const url = endpoint.startsWith(\"http\") ? endpoint : `${baseUrl}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as any));\n const message =\n error?.errorMessages?.join(\", \") || error?.message || response.statusText;\n\n throw new Error(`Jira API error: ${response.status} ${message}`);\n }\n\n if (response.status === 204) {\n return {} as T;\n }\n\n return response.json();\n}\n\nexport async function searchIssues(\n jql: string,\n options?: {\n fields?: string[];\n maxResults?: number;\n startAt?: number;\n },\n): Promise<{ issues: JiraIssue[]; total: number }> {\n const params = new URLSearchParams({\n jql,\n maxResults: String(options?.maxResults ?? 50),\n startAt: String(options?.startAt ?? 0),\n });\n\n if (options?.fields?.length) {\n params.set(\"fields\", options.fields.join(\",\"));\n }\n\n const response = await jiraFetch<JiraResponse<JiraIssue>>(\n `/search?${params.toString()}`,\n );\n\n return {\n issues: response.issues ?? [],\n total: response.total ?? 0,\n };\n}\n\nexport function getIssue(issueIdOrKey: string): Promise<JiraIssue> {\n return jiraFetch<JiraIssue>(`/issue/${issueIdOrKey}`);\n}\n\nexport async function createIssue(options: {\n projectKey: string;\n summary: string;\n description?: string;\n issueType: string;\n priority?: string;\n assigneeId?: string;\n labels?: string[];\n}): Promise<JiraIssue> {\n const fields: Record<string, unknown> = {\n project: { key: options.projectKey },\n summary: options.summary,\n issuetype: { name: options.issueType },\n };\n\n if (options.description) {\n fields.description = buildAdfDescription(options.description);\n }\n\n if (options.priority) {\n fields.priority = { name: options.priority };\n }\n\n if (options.assigneeId) {\n fields.assignee = { id: options.assigneeId };\n }\n\n if (options.labels?.length) {\n fields.labels = options.labels;\n }\n\n const response = await jiraFetch<{ id: string; key: string; self: string }>(\n \"/issue\",\n {\n method: \"POST\",\n body: JSON.stringify({ fields }),\n },\n );\n\n return getIssue(response.key);\n}\n\nexport function updateIssue(\n issueIdOrKey: string,\n updates: {\n summary?: string;\n description?: string;\n priority?: string;\n assigneeId?: string;\n labels?: string[];\n },\n): Promise<void> {\n const fields: Record<string, unknown> = {};\n\n if (updates.summary) {\n fields.summary = updates.summary;\n }\n\n if (updates.description) {\n fields.description = buildAdfDescription(updates.description);\n }\n\n if (updates.priority) {\n fields.priority = { name: updates.priority };\n }\n\n if (updates.assigneeId) {\n fields.assignee = { id: updates.assigneeId };\n }\n\n if (updates.labels) {\n fields.labels = updates.labels;\n }\n\n return jiraFetch<void>(`/issue/${issueIdOrKey}`, {\n method: \"PUT\",\n body: JSON.stringify({ fields }),\n });\n}\n\nexport async function transitionIssue(\n issueIdOrKey: string,\n transitionId: string,\n): Promise<void> {\n await jiraFetch<void>(`/issue/${issueIdOrKey}/transitions`, {\n method: \"POST\",\n body: JSON.stringify({ transition: { id: transitionId } }),\n });\n}\n\nexport async function getIssueTransitions(\n issueIdOrKey: string,\n): Promise<JiraTransition[]> {\n const response = await jiraFetch<{ transitions: JiraTransition[] }>(\n `/issue/${issueIdOrKey}/transitions`,\n );\n return response.transitions ?? [];\n}\n\nexport async function listProjects(): Promise<JiraProject[]> {\n return jiraFetch<JiraProject[]>(\"/project\");\n}\n\nexport function getProject(projectIdOrKey: string): Promise<JiraProject> {\n return jiraFetch<JiraProject>(`/project/${projectIdOrKey}`);\n}\n\nexport async function getProjectIssueTypes(\n projectIdOrKey: string,\n): Promise<JiraIssueType[]> {\n return jiraFetch<JiraIssueType[]>(`/project/${projectIdOrKey}/statuses`);\n}\n\nexport function extractDescriptionText(description: unknown): string {\n if (typeof description === \"string\") {\n return description;\n }\n\n if (!description || typeof description !== \"object\") {\n return \"\";\n }\n\n if (!(\"content\" in description)) {\n return \"\";\n }\n\n const content = (description as { content: unknown[] }).content;\n const texts: string[] = [];\n\n const extractText = (node: any): void => {\n if (node?.type === \"text\" && node.text) {\n texts.push(node.text);\n }\n if (Array.isArray(node?.content)) {\n node.content.forEach(extractText);\n }\n };\n\n content.forEach(extractText);\n return texts.join(\" \");\n}\n",
|
|
87
|
+
"tools/search-issues.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { extractDescriptionText, searchIssues } from \"../../lib/jira-client.ts\";\n\nexport default tool({\n id: \"search-issues\",\n description:\n 'Search for Jira issues using JQL (Jira Query Language). Returns matching issues with key details. Common JQL examples: \"assignee = currentUser() AND status != Done\", \"project = PROJ AND type = Bug\", \"created >= -7d\".',\n inputSchema: z.object({\n jql: z\n .string()\n .describe(\n 'JQL query string to search issues. Examples: \"assignee = currentUser()\", \"project = PROJ\", \"status = Open\"',\n ),\n maxResults: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of results to return\"),\n fields: z\n .array(z.string())\n .optional()\n .describe(\n 'Specific fields to include (e.g., [\"summary\", \"status\", \"assignee\"])',\n ),\n }),\n async execute({ jql, maxResults, fields }) {\n const result = await searchIssues(jql, { maxResults, fields });\n\n return {\n total: result.total,\n issues: result.issues.map((issue) => {\n const { fields } = issue;\n\n return {\n key: issue.key,\n id: issue.id,\n summary: fields.summary,\n description: extractDescriptionText(fields.description),\n status: fields.status.name,\n statusCategory: fields.status.statusCategory.name,\n type: fields.issuetype.name,\n priority: fields.priority?.name,\n assignee: fields.assignee?.displayName,\n reporter: fields.reporter?.displayName,\n project: {\n key: fields.project.key,\n name: fields.project.name,\n },\n created: fields.created,\n updated: fields.updated,\n labels: fields.labels ?? [],\n };\n }),\n };\n },\n});\n",
|
|
88
|
+
"tools/create-issue.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createIssue } from \"../../lib/jira-client.ts\";\n\nexport default tool({\n id: \"create-issue\",\n description:\n \"Create a new Jira issue in a project. Requires project key, summary, and issue type. Optionally set description, priority, assignee, and labels.\",\n inputSchema: z.object({\n projectKey: z.string().describe('The project key (e.g., \"PROJ\", \"DEV\")'),\n summary: z.string().describe(\"Brief summary/title of the issue\"),\n issueType: z.string().describe('Type of issue: \"Task\", \"Bug\", \"Story\", \"Epic\", etc.'),\n description: z.string().optional().describe(\"Detailed description of the issue\"),\n priority: z\n .string()\n .optional()\n .describe('Priority: \"Highest\", \"High\", \"Medium\", \"Low\", \"Lowest\"'),\n assigneeId: z.string().optional().describe(\"Atlassian account ID of the assignee (optional)\"),\n labels: z.array(z.string()).optional().describe(\"Array of labels to add to the issue\"),\n }),\n async execute({ projectKey, summary, issueType, description, priority, assigneeId, labels }) {\n const issue = await createIssue({\n projectKey,\n summary,\n issueType,\n description,\n priority,\n assigneeId,\n labels,\n });\n\n const { fields } = issue;\n\n return {\n key: issue.key,\n id: issue.id,\n summary: fields.summary,\n status: fields.status.name,\n type: fields.issuetype.name,\n priority: fields.priority?.name,\n assignee: fields.assignee?.displayName,\n project: {\n key: fields.project.key,\n name: fields.project.name,\n },\n created: fields.created,\n message: `Issue ${issue.key} created successfully`,\n };\n },\n});\n",
|
|
89
|
+
"tools/update-issue.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport {\n getIssue,\n getIssueTransitions,\n transitionIssue,\n updateIssue,\n} from \"../../lib/jira-client.ts\";\n\nexport default tool({\n id: \"update-issue\",\n description:\n 'Update an existing Jira issue. Can update fields like summary, description, priority, assignee, labels, or transition the status (e.g., move to \"In Progress\", \"Done\").',\n inputSchema: z.object({\n issueKey: z.string().describe('The issue key (e.g., \"PROJ-123\") to update'),\n summary: z.string().optional().describe(\"New summary/title for the issue\"),\n description: z.string().optional().describe(\"New description for the issue\"),\n priority: z\n .string()\n .optional()\n .describe('New priority: \"Highest\", \"High\", \"Medium\", \"Low\", \"Lowest\"'),\n assigneeId: z\n .string()\n .optional()\n .describe(\"Atlassian account ID of the new assignee\"),\n labels: z\n .array(z.string())\n .optional()\n .describe(\"New array of labels (replaces existing labels)\"),\n status: z\n .string()\n .optional()\n .describe('New status to transition to (e.g., \"In Progress\", \"Done\", \"To Do\")'),\n }),\n async execute({\n issueKey,\n summary,\n description,\n priority,\n assigneeId,\n labels,\n status,\n }) {\n const shouldUpdateFields =\n summary !== undefined ||\n description !== undefined ||\n priority !== undefined ||\n assigneeId !== undefined ||\n labels !== undefined;\n\n if (shouldUpdateFields) {\n await updateIssue(issueKey, {\n summary,\n description,\n priority,\n assigneeId,\n labels,\n });\n }\n\n if (status) {\n const transitions = await getIssueTransitions(issueKey);\n const normalizedStatus = status.toLowerCase();\n\n const targetTransition = transitions.find(\n (t) =>\n t.name.toLowerCase() === normalizedStatus ||\n t.to.name.toLowerCase() === normalizedStatus,\n );\n\n if (!targetTransition) {\n throw new Error(\n `Status \"${status}\" not found. Available transitions: ${transitions\n .map((t) => t.to.name)\n .join(\", \")}`,\n );\n }\n\n await transitionIssue(issueKey, targetTransition.id);\n }\n\n const updatedIssue = await getIssue(issueKey);\n\n return {\n key: updatedIssue.key,\n id: updatedIssue.id,\n summary: updatedIssue.fields.summary,\n status: updatedIssue.fields.status.name,\n type: updatedIssue.fields.issuetype.name,\n priority: updatedIssue.fields.priority?.name,\n assignee: updatedIssue.fields.assignee?.displayName,\n project: {\n key: updatedIssue.fields.project.key,\n name: updatedIssue.fields.project.name,\n },\n updated: updatedIssue.fields.updated,\n labels: updatedIssue.fields.labels ?? [],\n message: `Issue ${issueKey} updated successfully`,\n };\n },\n});\n",
|
|
90
|
+
"tools/list-projects.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listProjects } from \"../../lib/jira-client.ts\";\n\nexport default tool({\n id: \"list-projects\",\n description:\n \"List all accessible Jira projects in the connected site. Returns project keys, names, and basic information.\",\n inputSchema: z.object({}),\n async execute() {\n const projects = await listProjects();\n\n return {\n total: projects.length,\n projects: projects.map((project) => ({\n key: project.key,\n id: project.id,\n name: project.name,\n projectType: project.projectTypeKey,\n lead: project.lead\n ? {\n displayName: project.lead.displayName,\n accountId: project.lead.accountId,\n }\n : null,\n avatarUrl: project.avatarUrls?.[\"48x48\"],\n })),\n };\n },\n});\n",
|
|
91
|
+
"tools/get-issue.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { extractDescriptionText, getIssue } from \"../../lib/jira-client.ts\";\n\nexport default tool({\n id: \"get-issue\",\n description:\n \"Get detailed information about a specific Jira issue by its key (e.g., PROJ-123) or ID. Returns all fields including description, comments, history, etc.\",\n inputSchema: z.object({\n issueKey: z.string().describe('The issue key (e.g., \"PROJ-123\") or ID'),\n }),\n async execute({ issueKey }) {\n const issue = await getIssue(issueKey);\n const { fields } = issue;\n\n return {\n key: issue.key,\n id: issue.id,\n summary: fields.summary,\n description: extractDescriptionText(fields.description),\n status: fields.status.name,\n statusCategory: fields.status.statusCategory.name,\n type: {\n name: fields.issuetype.name,\n iconUrl: fields.issuetype.iconUrl,\n },\n priority: fields.priority\n ? {\n name: fields.priority.name,\n iconUrl: fields.priority.iconUrl,\n }\n : null,\n assignee: fields.assignee\n ? {\n displayName: fields.assignee.displayName,\n email: fields.assignee.emailAddress,\n accountId: fields.assignee.accountId,\n }\n : null,\n reporter: fields.reporter\n ? {\n displayName: fields.reporter.displayName,\n email: fields.reporter.emailAddress,\n accountId: fields.reporter.accountId,\n }\n : null,\n project: {\n key: fields.project.key,\n name: fields.project.name,\n id: fields.project.id,\n },\n created: fields.created,\n updated: fields.updated,\n labels: fields.labels ?? [],\n url: issue.self,\n };\n },\n});\n",
|
|
92
|
+
"app/api/auth/jira/route.ts": "import { createOAuthInitHandler, jiraConfig, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(jiraConfig, { tokenStore: memoryTokenStore });\n",
|
|
93
|
+
"app/api/auth/jira/callback/route.ts": "import { createOAuthCallbackHandler, jiraConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(jiraConfig, { tokenStore: hybridTokenStore });\n"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"integration:freshdesk": {
|
|
97
|
+
"files": {
|
|
98
|
+
"lib/freshdesk-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst FRESHDESK_BASE_URL = \"https://domain.freshdesk.com/api/v2\";\n\ninterface FreshdeskTicket {\n id: number;\n subject: string;\n description: string;\n description_text: string;\n status: number;\n priority: number;\n type: string;\n requester_id: number;\n responder_id: number | null;\n due_by: string;\n fr_due_by: string;\n created_at: string;\n updated_at: string;\n tags: string[];\n custom_fields: Record<string, unknown>;\n}\n\ninterface FreshdeskContact {\n id: number;\n name: string;\n email: string;\n phone: string | null;\n mobile: string | null;\n company_id: number | null;\n created_at: string;\n updated_at: string;\n tags: string[];\n custom_fields: Record<string, unknown>;\n}\n\nasync function freshdeskFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Freshdesk. Please connect your account.\");\n }\n\n const response = await fetch(`${FRESHDESK_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({}))) as { description?: string };\n throw new Error(\n `Freshdesk API error: ${response.status} ${error.description ?? response.statusText}`,\n );\n }\n\n return response.json() as Promise<T>;\n}\n\nfunction buildEndpoint(path: string, params: URLSearchParams): string {\n const queryString = params.toString();\n return queryString ? `${path}?${queryString}` : path;\n}\n\nexport async function listTickets(\n options: {\n status?: number;\n priority?: number;\n type?: string;\n page?: number;\n perPage?: number;\n } = {},\n): Promise<FreshdeskTicket[]> {\n const params = new URLSearchParams();\n\n if (options.status !== undefined) params.set(\"status\", String(options.status));\n if (options.priority !== undefined) params.set(\"priority\", String(options.priority));\n if (options.type) params.set(\"type\", options.type);\n if (options.page) params.set(\"page\", String(options.page));\n if (options.perPage) params.set(\"per_page\", String(options.perPage));\n\n return freshdeskFetch<FreshdeskTicket[]>(buildEndpoint(\"/tickets\", params));\n}\n\nexport async function getTicket(ticketId: number): Promise<FreshdeskTicket> {\n return freshdeskFetch<FreshdeskTicket>(`/tickets/${ticketId}`);\n}\n\nexport async function createTicket(options: {\n subject: string;\n description: string;\n email: string;\n priority?: number;\n status?: number;\n type?: string;\n tags?: string[];\n}): Promise<FreshdeskTicket> {\n const body: Record<string, unknown> = {\n subject: options.subject,\n description: options.description,\n email: options.email,\n priority: options.priority ?? 1,\n status: options.status ?? 2,\n ...(options.type ? { type: options.type } : {}),\n ...(options.tags ? { tags: options.tags } : {}),\n };\n\n return freshdeskFetch<FreshdeskTicket>(\"/tickets\", {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport async function updateTicket(\n ticketId: number,\n updates: {\n subject?: string;\n description?: string;\n status?: number;\n priority?: number;\n type?: string;\n tags?: string[];\n },\n): Promise<FreshdeskTicket> {\n const body: Record<string, unknown> = {\n ...(updates.subject !== undefined ? { subject: updates.subject } : {}),\n ...(updates.description !== undefined ? { description: updates.description } : {}),\n ...(updates.status !== undefined ? { status: updates.status } : {}),\n ...(updates.priority !== undefined ? { priority: updates.priority } : {}),\n ...(updates.type !== undefined ? { type: updates.type } : {}),\n ...(updates.tags !== undefined ? { tags: updates.tags } : {}),\n };\n\n return freshdeskFetch<FreshdeskTicket>(`/tickets/${ticketId}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n });\n}\n\nexport async function listContacts(\n options: {\n email?: string;\n mobile?: string;\n phone?: string;\n companyId?: number;\n page?: number;\n perPage?: number;\n } = {},\n): Promise<FreshdeskContact[]> {\n const params = new URLSearchParams();\n\n if (options.email) params.set(\"email\", options.email);\n if (options.mobile) params.set(\"mobile\", options.mobile);\n if (options.phone) params.set(\"phone\", options.phone);\n if (options.companyId) params.set(\"company_id\", String(options.companyId));\n if (options.page) params.set(\"page\", String(options.page));\n if (options.perPage) params.set(\"per_page\", String(options.perPage));\n\n return freshdeskFetch<FreshdeskContact[]>(buildEndpoint(\"/contacts\", params));\n}\n\nexport const TicketStatus = {\n OPEN: 2,\n PENDING: 3,\n RESOLVED: 4,\n CLOSED: 5,\n} as const;\n\nexport const TicketPriority = {\n LOW: 1,\n MEDIUM: 2,\n HIGH: 3,\n URGENT: 4,\n} as const;\n",
|
|
99
|
+
"tools/create-ticket.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createTicket, TicketPriority, TicketStatus } from \"../../lib/freshdesk-client.ts\";\n\nconst priorityMap = {\n low: TicketPriority.LOW,\n medium: TicketPriority.MEDIUM,\n high: TicketPriority.HIGH,\n urgent: TicketPriority.URGENT,\n} as const;\n\nconst statusMap = {\n open: TicketStatus.OPEN,\n pending: TicketStatus.PENDING,\n} as const;\n\nfunction getKeyByValue<T extends Record<string, unknown>>(map: T, value: T[keyof T]): string {\n return Object.keys(map).find((key) => map[key as keyof T] === value) ?? \"unknown\";\n}\n\nexport default tool({\n id: \"create-ticket\",\n description: \"Create a new support ticket in Freshdesk.\",\n inputSchema: z.object({\n subject: z.string().describe(\"The subject/title of the ticket\"),\n description: z.string().describe(\"Description or details of the ticket\"),\n email: z.string().email().describe(\"Email address of the requester\"),\n priority: z\n .enum([\"low\", \"medium\", \"high\", \"urgent\"])\n .default(\"medium\")\n .describe(\"Priority level of the ticket\"),\n status: z.enum([\"open\", \"pending\"]).default(\"open\").describe(\"Initial status of the ticket\"),\n type: z\n .string()\n .optional()\n .describe(\"Type of ticket (e.g., 'Question', 'Incident', 'Problem', 'Feature Request')\"),\n tags: z.array(z.string()).optional().describe(\"Tags to add to the ticket\"),\n }),\n async execute({ subject, description, email, priority, status, type, tags }) {\n const ticket = await createTicket({\n subject,\n description,\n email,\n priority: priorityMap[priority],\n status: statusMap[status],\n type,\n tags,\n });\n\n return {\n success: true,\n ticket: {\n id: ticket.id,\n subject: ticket.subject,\n status: getKeyByValue(statusMap, ticket.status),\n priority: getKeyByValue(priorityMap, ticket.priority),\n createdAt: ticket.created_at,\n },\n };\n },\n});\n",
|
|
100
|
+
"tools/get-ticket.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getTicket, TicketPriority, TicketStatus } from \"../../lib/freshdesk-client.ts\";\n\nconst statusMap: Record<number, string> = {\n [TicketStatus.OPEN]: \"open\",\n [TicketStatus.PENDING]: \"pending\",\n [TicketStatus.RESOLVED]: \"resolved\",\n [TicketStatus.CLOSED]: \"closed\",\n};\n\nconst priorityMap: Record<number, string> = {\n [TicketPriority.LOW]: \"low\",\n [TicketPriority.MEDIUM]: \"medium\",\n [TicketPriority.HIGH]: \"high\",\n [TicketPriority.URGENT]: \"urgent\",\n};\n\nexport default tool({\n id: \"get-ticket\",\n description: \"Get details of a specific Freshdesk support ticket by its ID.\",\n inputSchema: z.object({\n ticketId: z.number().describe(\"The ID of the ticket to retrieve\"),\n }),\n async execute({ ticketId }) {\n const ticket = await getTicket(ticketId);\n\n return {\n id: ticket.id,\n subject: ticket.subject,\n description: ticket.description,\n descriptionText: ticket.description_text,\n status: statusMap[ticket.status] ?? \"unknown\",\n priority: priorityMap[ticket.priority] ?? \"unknown\",\n type: ticket.type,\n requesterId: ticket.requester_id,\n responderId: ticket.responder_id,\n dueBy: ticket.due_by,\n firstResponseDueBy: ticket.fr_due_by,\n createdAt: ticket.created_at,\n updatedAt: ticket.updated_at,\n tags: ticket.tags,\n customFields: ticket.custom_fields,\n };\n },\n});\n",
|
|
101
|
+
"tools/update-ticket.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { updateTicket, TicketPriority, TicketStatus } from \"../../lib/freshdesk-client.ts\";\n\nconst priorityMap = {\n low: TicketPriority.LOW,\n medium: TicketPriority.MEDIUM,\n high: TicketPriority.HIGH,\n urgent: TicketPriority.URGENT,\n} as const;\n\nconst statusMap = {\n open: TicketStatus.OPEN,\n pending: TicketStatus.PENDING,\n resolved: TicketStatus.RESOLVED,\n closed: TicketStatus.CLOSED,\n} as const;\n\nfunction getKeyByValue<T extends Record<string, unknown>>(map: T, value: T[keyof T]): string {\n return Object.keys(map).find((key) => map[key as keyof T] === value) ?? \"unknown\";\n}\n\nexport default tool({\n id: \"update-ticket\",\n description: \"Update an existing Freshdesk support ticket.\",\n inputSchema: z.object({\n ticketId: z.number().describe(\"The ID of the ticket to update\"),\n subject: z.string().optional().describe(\"New subject/title for the ticket\"),\n description: z.string().optional().describe(\"New description or details\"),\n status: z.enum([\"open\", \"pending\", \"resolved\", \"closed\"]).optional().describe(\"New status for the ticket\"),\n priority: z.enum([\"low\", \"medium\", \"high\", \"urgent\"]).optional().describe(\"New priority level\"),\n type: z\n .string()\n .optional()\n .describe(\"New type of ticket (e.g., 'Question', 'Incident', 'Problem', 'Feature Request')\"),\n tags: z.array(z.string()).optional().describe(\"New tags for the ticket (replaces existing tags)\"),\n }),\n async execute({ ticketId, subject, description, status, priority, type, tags }) {\n const ticket = await updateTicket(ticketId, {\n subject,\n description,\n status: status ? statusMap[status] : undefined,\n priority: priority ? priorityMap[priority] : undefined,\n type,\n tags,\n });\n\n return {\n success: true,\n ticket: {\n id: ticket.id,\n subject: ticket.subject,\n status: getKeyByValue(statusMap, ticket.status),\n priority: getKeyByValue(priorityMap, ticket.priority),\n updatedAt: ticket.updated_at,\n },\n };\n },\n});\n",
|
|
102
|
+
"tools/list-contacts.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listContacts } from \"../../lib/freshdesk-client.ts\";\n\nexport default tool({\n id: \"list-contacts\",\n description:\n \"List customer contacts from Freshdesk. Can filter by email, phone, mobile, or company ID.\",\n inputSchema: z.object({\n email: z.string().optional().describe(\"Filter by contact email address\"),\n phone: z.string().optional().describe(\"Filter by contact phone number\"),\n mobile: z.string().optional().describe(\"Filter by contact mobile number\"),\n companyId: z.number().optional().describe(\"Filter by company ID\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(30)\n .describe(\"Maximum number of contacts to return\"),\n }),\n async execute({ email, phone, mobile, companyId, limit }) {\n const contacts = await listContacts({ email, phone, mobile, companyId, perPage: limit });\n\n return contacts.map(\n ({ id, name, email, phone, mobile, company_id, created_at, updated_at, tags }) => ({\n id,\n name,\n email,\n phone,\n mobile,\n companyId: company_id,\n createdAt: created_at,\n updatedAt: updated_at,\n tags,\n }),\n );\n },\n});\n",
|
|
103
|
+
"tools/list-tickets.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listTickets, TicketPriority, TicketStatus } from \"../../lib/freshdesk-client.ts\";\n\nconst statusMap = {\n open: TicketStatus.OPEN,\n pending: TicketStatus.PENDING,\n resolved: TicketStatus.RESOLVED,\n closed: TicketStatus.CLOSED,\n} as const;\n\nconst priorityMap = {\n low: TicketPriority.LOW,\n medium: TicketPriority.MEDIUM,\n high: TicketPriority.HIGH,\n urgent: TicketPriority.URGENT,\n} as const;\n\nfunction getKeyByValue<T extends Record<string, unknown>>(\n map: T,\n value: T[keyof T],\n): string {\n return Object.keys(map).find((key) => map[key as keyof T] === value) ?? \"unknown\";\n}\n\nexport default tool({\n id: \"list-tickets\",\n description:\n \"List support tickets from Freshdesk. Can filter by status, priority, and type.\",\n inputSchema: z.object({\n status: z\n .enum([\"open\", \"pending\", \"resolved\", \"closed\"])\n .optional()\n .describe(\"Filter by ticket status\"),\n priority: z\n .enum([\"low\", \"medium\", \"high\", \"urgent\"])\n .optional()\n .describe(\"Filter by ticket priority\"),\n type: z\n .string()\n .optional()\n .describe(\n \"Filter by ticket type (e.g., 'Question', 'Incident', 'Problem', 'Feature Request')\",\n ),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(30)\n .describe(\"Maximum number of tickets to return\"),\n }),\n async execute({ status, priority, type, limit }) {\n const tickets = await listTickets({\n status: status ? statusMap[status] : undefined,\n priority: priority ? priorityMap[priority] : undefined,\n type,\n perPage: limit,\n });\n\n return tickets.map((ticket) => ({\n id: ticket.id,\n subject: ticket.subject,\n description: ticket.description_text,\n status: getKeyByValue(statusMap, ticket.status),\n priority: getKeyByValue(priorityMap, ticket.priority),\n type: ticket.type,\n dueBy: ticket.due_by,\n createdAt: ticket.created_at,\n updatedAt: ticket.updated_at,\n tags: ticket.tags,\n }));\n },\n});\n",
|
|
104
|
+
"app/api/auth/freshdesk/route.ts": "import { createOAuthInitHandler, freshdeskConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(freshdeskConfig);\n",
|
|
105
|
+
"app/api/auth/freshdesk/callback/route.ts": "/**\n * Freshdesk OAuth Callback\n *\n * Handles the OAuth callback from Freshdesk and stores the tokens.\n */\n\nimport { createOAuthCallbackHandler, freshdeskConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\ntype Tokens = { accessToken: string; refreshToken?: string; expiresAt?: number };\ntype State = { state: string; codeVerifier?: string; createdAt: number };\n\n// Hybrid adapter: uses framework's memoryTokenStore for state (PKCE),\n// but user's tokenStore for actual token storage\nconst hybridTokenStore = {\n async getTokens(serviceId: string): Promise<Tokens | null> {\n return tokenStore.getToken(\"current-user\", serviceId);\n },\n async setTokens(serviceId: string, tokens: Tokens): Promise<void> {\n await tokenStore.setToken(\"current-user\", serviceId, tokens);\n },\n async clearTokens(serviceId: string): Promise<void> {\n await tokenStore.revokeToken(\"current-user\", serviceId);\n },\n getState(state: string): ReturnType<typeof memoryTokenStore.getState> {\n return memoryTokenStore.getState(state);\n },\n setState(state: State): ReturnType<typeof memoryTokenStore.setState> {\n return memoryTokenStore.setState(state);\n },\n clearState(state: string): ReturnType<typeof memoryTokenStore.clearState> {\n return memoryTokenStore.clearState(state);\n },\n};\n\nexport const GET = createOAuthCallbackHandler(freshdeskConfig, { tokenStore: hybridTokenStore });\n",
|
|
106
|
+
".env.example": "# Freshdesk OAuth Configuration\n# Get your credentials from https://developers.freshdesk.com/apps/\nFRESHDESK_CLIENT_ID=your-client-id\nFRESHDESK_CLIENT_SECRET=your-client-secret\n"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"integration:mixpanel": {
|
|
110
|
+
"files": {
|
|
111
|
+
"lib/mixpanel-client.ts": "import { getApiSecret, getProjectId, getProjectToken } from \"./token-store.ts\";\n\nconst MIXPANEL_API_BASE = \"https://mixpanel.com/api\";\nconst MIXPANEL_TRACK_BASE = \"https://api.mixpanel.com\";\nconst MIXPANEL_DATA_BASE = \"https://data.mixpanel.com/api/2.0\";\n\nexport interface MixpanelEvent {\n event: string;\n properties: Record<string, unknown>;\n}\n\nexport interface MixpanelEventQuery {\n event?: string;\n from_date: string;\n to_date: string;\n where?: string;\n limit?: number;\n}\n\nexport interface MixpanelEventResult {\n event: string;\n properties: Record<string, unknown>;\n}\n\nexport interface MixpanelFunnel {\n funnel_id: number;\n name: string;\n steps: Array<{\n event: string;\n count: number;\n avg_time: number | null;\n overall_conv_ratio: number;\n step_conv_ratio: number;\n }>;\n data: {\n series: string[];\n values: Record<string, number[]>;\n };\n}\n\nexport interface MixpanelRetention {\n date: string;\n count: number;\n retention: Array<{\n day: number;\n count: number;\n rate: number;\n }>;\n}\n\nexport interface MixpanelCohort {\n id: number;\n name: string;\n description: string;\n count: number;\n created: string;\n is_visible: boolean;\n project_id: number;\n}\n\ninterface MixpanelError {\n error: string;\n request: string;\n}\n\nfunction getAuthHeader(): string {\n const apiSecret = getApiSecret();\n if (!apiSecret) {\n throw new Error(\n \"Not authenticated with Mixpanel. Please set MIXPANEL_API_SECRET.\",\n );\n }\n\n // Mixpanel uses Basic auth with API secret as username and empty password\n return `Basic ${btoa(`${apiSecret}:`)}`;\n}\n\nasync function mixpanelFetch<T>(\n baseUrl: string,\n endpoint: string,\n options: RequestInit & { params?: Record<string, string | number | boolean> } =\n {},\n): Promise<T> {\n const url = new URL(`${baseUrl}${endpoint}`);\n\n if (options.params) {\n for (const [key, value] of Object.entries(options.params)) {\n url.searchParams.append(key, String(value));\n }\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...(options.headers as Record<string, string> | undefined),\n };\n\n if (baseUrl === MIXPANEL_DATA_BASE || baseUrl === MIXPANEL_API_BASE) {\n headers.Authorization = getAuthHeader();\n }\n\n const response = await fetch(url.toString(), { ...options, headers });\n\n if (!response.ok) {\n let errorMessage = `Mixpanel API error: ${response.status} ${response.statusText}`;\n\n try {\n const errorData = (await response.json()) as MixpanelError;\n if (errorData.error) errorMessage = `Mixpanel API error: ${errorData.error}`;\n } catch {\n // If parsing JSON fails, use default error message\n }\n\n throw new Error(errorMessage);\n }\n\n return (await response.json()) as T;\n}\n\nexport async function trackEvent(\n event: string,\n properties: Record<string, unknown>,\n distinctId: string,\n): Promise<{ status: number; error?: string }> {\n const projectToken = getProjectToken();\n if (!projectToken) {\n throw new Error(\n \"Not authenticated with Mixpanel. Please set MIXPANEL_PROJECT_TOKEN.\",\n );\n }\n\n const payload = {\n event,\n properties: {\n ...properties,\n token: projectToken,\n distinct_id: distinctId,\n time: Date.now(),\n },\n };\n\n const response = await fetch(`${MIXPANEL_TRACK_BASE}/track`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify([payload]),\n });\n\n if (!response.ok) {\n const text = await response.text();\n return {\n status: 0,\n error: `Failed to track event: ${response.status} ${text}`,\n };\n }\n\n return (await response.json()) as { status: number; error?: string };\n}\n\nexport async function queryEvents(\n from: string,\n to: string,\n event?: string,\n): Promise<MixpanelEventResult[]> {\n const projectId = getProjectId();\n if (!projectId) {\n throw new Error(\"Project ID not set. Please set MIXPANEL_PROJECT_ID.\");\n }\n\n const params: Record<string, string> = { from_date: from, to_date: to };\n if (event) params.event = JSON.stringify([event]);\n\n const response = await mixpanelFetch<string[]>(\n MIXPANEL_DATA_BASE,\n \"/export\",\n { params },\n );\n\n const events: MixpanelEventResult[] = [];\n if (!Array.isArray(response)) return events;\n\n for (const line of response) {\n if (typeof line !== \"string\" || !line.trim()) continue;\n\n try {\n const parsed = JSON.parse(line) as MixpanelEventResult;\n events.push({ event: parsed.event, properties: parsed.properties });\n } catch {\n // Skip malformed lines\n }\n }\n\n return events;\n}\n\nexport async function getFunnel(\n funnelId: number,\n from: string,\n to: string,\n): Promise<MixpanelFunnel> {\n const params: Record<string, string | number> = {\n funnel_id: funnelId,\n from_date: from,\n to_date: to,\n unit: \"day\",\n };\n\n return mixpanelFetch<MixpanelFunnel>(MIXPANEL_DATA_BASE, \"/funnels\", {\n params,\n });\n}\n\nexport async function getRetention(\n from: string,\n to: string,\n event: string,\n retentionType: \"birth\" | \"compounded\" = \"birth\",\n): Promise<MixpanelRetention[]> {\n const params: Record<string, string> = {\n from_date: from,\n to_date: to,\n retention_type: retentionType,\n born_event: event,\n event,\n unit: \"day\",\n };\n\n const response = await mixpanelFetch<Record<string, MixpanelRetention>>(\n MIXPANEL_DATA_BASE,\n \"/retention\",\n { params },\n );\n\n return Object.entries(response).map(([date, data]) => ({ date, ...data }));\n}\n\nexport async function listCohorts(): Promise<MixpanelCohort[]> {\n const projectId = getProjectId();\n if (!projectId) {\n throw new Error(\"Project ID not set. Please set MIXPANEL_PROJECT_ID.\");\n }\n\n return mixpanelFetch<MixpanelCohort[]>(\n MIXPANEL_API_BASE,\n \"/2.0/cohorts/list\",\n { params: { project_id: projectId } },\n );\n}\n\nexport function formatDate(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\nexport function getDateRange(days: number): { from: string; to: string } {\n const to = new Date();\n const from = new Date();\n from.setDate(from.getDate() - days);\n\n return { from: formatDate(from), to: formatDate(to) };\n}\n\nexport function calculateFunnelConversionRate(funnel: MixpanelFunnel): number {\n if (!funnel.steps || funnel.steps.length < 2) return 0;\n\n const firstStep = funnel.steps[0];\n const lastStep = funnel.steps[funnel.steps.length - 1];\n\n if (!firstStep || !lastStep || firstStep.count === 0) return 0;\n\n return (lastStep.count / firstStep.count) * 100;\n}\n",
|
|
112
|
+
"tools/get-funnel.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { calculateFunnelConversionRate, getFunnel } from \"../../lib/mixpanel-client.ts\";\n\nexport default tool({\n id: \"get-funnel\",\n description:\n \"Retrieve funnel analysis data from Mixpanel. Analyze conversion rates and user drop-off at each step of a funnel.\",\n inputSchema: z.object({\n funnelId: z\n .number()\n .describe(\"The numeric ID of the funnel (found in Mixpanel funnel URL or settings)\"),\n from: z.string().describe(\"Start date in YYYY-MM-DD format (e.g., '2024-01-01')\"),\n to: z.string().describe(\"End date in YYYY-MM-DD format (e.g., '2024-01-31')\"),\n }),\n async execute({ funnelId, from, to }) {\n const funnel = await getFunnel(funnelId, from, to);\n const overallConversionRate = calculateFunnelConversionRate(funnel);\n\n return {\n funnelId: funnel.funnel_id,\n name: funnel.name,\n dateRange: { from, to },\n overallConversionRate: `${overallConversionRate.toFixed(2)}%`,\n steps: funnel.steps.map((step, index) => ({\n stepNumber: index + 1,\n event: step.event,\n count: step.count,\n overallConversionRate: `${(step.overall_conv_ratio * 100).toFixed(2)}%`,\n stepConversionRate: `${(step.step_conv_ratio * 100).toFixed(2)}%`,\n averageTime: step.avg_time ? `${(step.avg_time / 60).toFixed(1)} minutes` : \"N/A\",\n })),\n data: funnel.data,\n };\n },\n});\n",
|
|
113
|
+
"tools/track-event.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { trackEvent } from \"../../lib/mixpanel-client.ts\";\n\nexport default tool({\n id: \"track-event\",\n description:\n \"Track a custom event in Mixpanel. Capture user actions, page views, or any custom analytics event with properties.\",\n inputSchema: z.object({\n event: z\n .string()\n .describe(\n \"Event name (e.g., 'Button Clicked', 'Page Viewed', 'Purchase Completed')\",\n ),\n distinctId: z\n .string()\n .describe(\n \"Unique identifier for the user or session (e.g., user ID, email, or anonymous ID)\",\n ),\n properties: z\n .record(z.unknown())\n .optional()\n .describe(\n \"Additional properties to attach to the event (e.g., {product_id: '123', price: 29.99, category: 'electronics'})\",\n ),\n }),\n async execute({ event, distinctId, properties }) {\n const eventProperties = properties ?? {};\n const result = await trackEvent(event, eventProperties, distinctId);\n\n if (result.status !== 1) {\n return {\n success: false,\n error: result.error ?? \"Failed to track event\",\n };\n }\n\n return {\n success: true,\n event: {\n name: event,\n distinctId,\n properties: eventProperties,\n timestamp: new Date().toISOString(),\n },\n message: \"Event tracked successfully\",\n };\n },\n});\n",
|
|
114
|
+
"tools/list-cohorts.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listCohorts } from \"../../lib/mixpanel-client.ts\";\n\nexport default tool({\n id: \"list-cohorts\",\n description:\n \"List all user cohorts defined in your Mixpanel project. Cohorts are saved user segments based on properties or behaviors.\",\n inputSchema: z.object({\n includeHidden: z\n .boolean()\n .optional()\n .default(false)\n .describe(\"Include hidden cohorts in the results (defaults to false)\"),\n }),\n async execute({ includeHidden }) {\n const allCohorts = await listCohorts();\n const cohorts = includeHidden\n ? allCohorts\n : allCohorts.filter((c) => c.is_visible);\n\n const totalUsers = cohorts.reduce((sum, c) => sum + c.count, 0);\n\n let largestCohort = \"N/A\";\n let smallestCohort = \"N/A\";\n\n if (cohorts.length > 0) {\n let largest = cohorts[0];\n let smallest = cohorts[0];\n\n for (const c of cohorts) {\n if (c.count > largest.count) largest = c;\n if (c.count < smallest.count) smallest = c;\n }\n\n largestCohort = largest.name;\n smallestCohort = smallest.name;\n }\n\n return {\n total: cohorts.length,\n cohorts: cohorts.map((cohort) => ({\n id: cohort.id,\n name: cohort.name,\n description: cohort.description,\n count: cohort.count,\n created: cohort.created,\n isVisible: cohort.is_visible,\n projectId: cohort.project_id,\n })),\n summary: {\n totalUsers,\n largestCohort,\n smallestCohort,\n },\n };\n },\n});\n",
|
|
115
|
+
"tools/query-events.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { queryEvents } from \"../../lib/mixpanel-client.ts\";\n\nexport default tool({\n id: \"query-events\",\n description:\n \"Query and export event data from Mixpanel. Retrieve events within a date range, optionally filtered by event name.\",\n inputSchema: z.object({\n from: z\n .string()\n .describe(\"Start date in YYYY-MM-DD format (e.g., '2024-01-01')\"),\n to: z\n .string()\n .describe(\"End date in YYYY-MM-DD format (e.g., '2024-01-31')\"),\n event: z\n .string()\n .optional()\n .describe(\n \"Optional: Filter by specific event name (e.g., 'Page Viewed'). If not provided, returns all events.\",\n ),\n limit: z\n .number()\n .optional()\n .default(100)\n .describe(\"Maximum number of events to return (defaults to 100)\"),\n }),\n async execute({ from, to, event, limit }) {\n const events = await queryEvents(from, to, event);\n const limitedEvents = events.slice(0, limit);\n\n return {\n total: events.length,\n returned: limitedEvents.length,\n dateRange: { from, to },\n eventFilter: event ?? \"all\",\n events: limitedEvents.map(({ event, properties }) => ({\n event,\n properties,\n })),\n };\n },\n});\n",
|
|
116
|
+
"tools/get-retention.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getRetention } from \"../../lib/mixpanel-client.ts\";\n\nfunction formatRate(rate: number): string {\n return `${(rate * 100).toFixed(2)}%`;\n}\n\nfunction averageRetention(retention: Array<{ retention: Array<{ day: number; rate: number }> }>, day: number): string {\n if (retention.length === 0) return \"N/A\";\n\n const total = retention.reduce((sum, cohort) => {\n const rate = cohort.retention.find((r) => r.day === day)?.rate ?? 0;\n return sum + rate;\n }, 0);\n\n return formatRate(total / retention.length);\n}\n\nexport default tool({\n id: \"get-retention\",\n description:\n \"Analyze user retention cohorts in Mixpanel. Understand how many users return after performing an initial event.\",\n inputSchema: z.object({\n from: z.string().describe(\"Start date in YYYY-MM-DD format (e.g., '2024-01-01')\"),\n to: z.string().describe(\"End date in YYYY-MM-DD format (e.g., '2024-01-31')\"),\n event: z.string().describe(\"The event to analyze retention for (e.g., 'App Opened', 'Sign Up')\"),\n retentionType: z\n .enum([\"birth\", \"compounded\"])\n .optional()\n .default(\"birth\")\n .describe(\"Retention type: 'birth' (first time users) or 'compounded' (all users who did the event)\"),\n }),\n async execute({ from, to, event, retentionType }): Promise<{\n event: string;\n retentionType: \"birth\" | \"compounded\";\n dateRange: { from: string; to: string };\n cohorts: Array<{\n date: string;\n initialCount: number;\n retention: Array<{ day: number; count: number; rate: string }>;\n }>;\n summary: {\n totalCohorts: number;\n averageDay1Retention: string;\n averageDay7Retention: string;\n };\n }> {\n const retention = await getRetention(from, to, event, retentionType);\n\n return {\n event,\n retentionType,\n dateRange: { from, to },\n cohorts: retention.map((cohort) => ({\n date: cohort.date,\n initialCount: cohort.count,\n retention: cohort.retention.map((r) => ({\n day: r.day,\n count: r.count,\n rate: formatRate(r.rate),\n })),\n })),\n summary: {\n totalCohorts: retention.length,\n averageDay1Retention: averageRetention(retention, 1),\n averageDay7Retention: averageRetention(retention, 7),\n },\n };\n },\n});\n",
|
|
117
|
+
".env.example": "# Mixpanel Integration\n# Get your credentials at https://mixpanel.com/settings/project\n\n# Project Token - used for tracking events (ingestion)\nMIXPANEL_PROJECT_TOKEN=your_project_token_here\n\n# API Secret - used for querying and exporting data\nMIXPANEL_API_SECRET=your_api_secret_here\n\n# Project ID - found in your project settings URL\nMIXPANEL_PROJECT_ID=your_project_id_here\n"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"integration:calendar": {
|
|
121
|
+
"files": {
|
|
122
|
+
"lib/calendar-client.ts": "/**\n * Google Calendar API Client\n *\n * Provides a type-safe interface to Google Calendar API operations.\n */\n\nimport { getValidToken } from \"./oauth.ts\";\n\n// Helper for Cross-Platform environment access\nfunction getEnv(key: string): string | undefined {\n // @ts-ignore - Deno global\n if (typeof Deno !== \"undefined\") {\n // @ts-ignore - Deno global\n return Deno.env.get(key);\n }\n // @ts-ignore - process global\n if (typeof process !== \"undefined\" && process.env) {\n // @ts-ignore - process global\n return process.env[key];\n }\n return undefined;\n}\n\nconst CALENDAR_API_BASE = \"https://www.googleapis.com/calendar/v3\";\n\nexport interface CalendarEvent {\n id: string;\n summary: string;\n description?: string;\n location?: string;\n start: {\n dateTime?: string;\n date?: string;\n timeZone?: string;\n };\n end: {\n dateTime?: string;\n date?: string;\n timeZone?: string;\n };\n attendees?: Array<{\n email: string;\n responseStatus: \"needsAction\" | \"declined\" | \"tentative\" | \"accepted\";\n displayName?: string;\n }>;\n htmlLink: string;\n status: \"confirmed\" | \"tentative\" | \"cancelled\";\n organizer?: { email: string; displayName?: string };\n}\n\nexport interface CreateEventOptions {\n summary: string;\n description?: string;\n location?: string;\n start: Date | string;\n end: Date | string;\n attendees?: string[];\n timeZone?: string;\n}\n\nexport interface FreeBusySlot {\n start: string;\n end: string;\n}\n\n/**\n * Google Calendar OAuth provider configuration\n */\nexport const calendarOAuthProvider = {\n name: \"calendar\",\n authorizationUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n tokenUrl: \"https://oauth2.googleapis.com/token\",\n clientId: getEnv(\"GOOGLE_CLIENT_ID\") ?? \"\",\n clientSecret: getEnv(\"GOOGLE_CLIENT_SECRET\") ?? \"\",\n scopes: [\n \"https://www.googleapis.com/auth/calendar.readonly\",\n \"https://www.googleapis.com/auth/calendar.events\",\n ],\n callbackPath: \"/api/auth/calendar/callback\",\n};\n\n/**\n * Create a Calendar client for a specific user\n */\nexport function createCalendarClient(userId: string): {\n listEvents(options?: {\n maxResults?: number;\n timeMin?: Date | string;\n timeMax?: Date | string;\n calendarId?: string;\n }): Promise<CalendarEvent[]>;\n getTodayEvents(): Promise<CalendarEvent[]>;\n createEvent(options: CreateEventOptions, calendarId?: string): Promise<CalendarEvent>;\n getFreeBusy(options: {\n timeMin: Date | string;\n timeMax: Date | string;\n calendarId?: string;\n }): Promise<FreeBusySlot[]>;\n findFreeSlots(options: {\n timeMin: Date | string;\n timeMax: Date | string;\n durationMinutes: number;\n calendarId?: string;\n }): Promise<Array<{ start: Date; end: Date }>>;\n deleteEvent(eventId: string, calendarId?: string): Promise<void>;\n} {\n async function getAccessToken(): Promise<string> {\n const token = await getValidToken(calendarOAuthProvider, userId, \"calendar\");\n if (!token) {\n throw new Error(\"Calendar not connected. Please connect your Google Calendar first.\");\n }\n return token;\n }\n\n async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const accessToken = await getAccessToken();\n\n const response = await fetch(`${CALENDAR_API_BASE}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Calendar API error: ${response.status} - ${error}`);\n }\n\n return response.json();\n }\n\n async function listEvents(options: {\n maxResults?: number;\n timeMin?: Date | string;\n timeMax?: Date | string;\n calendarId?: string;\n } = {}): Promise<CalendarEvent[]> {\n const params = new URLSearchParams();\n\n params.set(\n \"timeMin\",\n options.timeMin ? new Date(options.timeMin).toISOString() : new Date().toISOString(),\n );\n\n if (options.timeMax) {\n params.set(\"timeMax\", new Date(options.timeMax).toISOString());\n }\n\n params.set(\"maxResults\", String(options.maxResults ?? 10));\n params.set(\"singleEvents\", \"true\");\n params.set(\"orderBy\", \"startTime\");\n\n const calendarId = encodeURIComponent(options.calendarId ?? \"primary\");\n const result = await apiRequest<{ items: CalendarEvent[] }>(\n `/calendars/${calendarId}/events?${params.toString()}`,\n );\n\n return result.items ?? [];\n }\n\n function getTodayEvents(): Promise<CalendarEvent[]> {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n\n const tomorrow = new Date(today);\n tomorrow.setDate(tomorrow.getDate() + 1);\n\n return listEvents({\n timeMin: today,\n timeMax: tomorrow,\n maxResults: 50,\n });\n }\n\n function createEvent(options: CreateEventOptions, calendarId = \"primary\"): Promise<CalendarEvent> {\n const startDate = typeof options.start === \"string\" ? options.start : options.start.toISOString();\n const endDate = typeof options.end === \"string\" ? options.end : options.end.toISOString();\n const timeZone = options.timeZone ?? \"UTC\";\n\n const event = {\n summary: options.summary,\n description: options.description,\n location: options.location,\n start: {\n dateTime: startDate,\n timeZone,\n },\n end: {\n dateTime: endDate,\n timeZone,\n },\n attendees: options.attendees?.map((email) => ({ email })),\n };\n\n return apiRequest<CalendarEvent>(`/calendars/${encodeURIComponent(calendarId)}/events`, {\n method: \"POST\",\n body: JSON.stringify(event),\n });\n }\n\n async function getFreeBusy(options: {\n timeMin: Date | string;\n timeMax: Date | string;\n calendarId?: string;\n }): Promise<FreeBusySlot[]> {\n const calendarId = options.calendarId ?? \"primary\";\n\n const result = await apiRequest<{\n calendars: Record<string, { busy: FreeBusySlot[] }>;\n }>(\"/freeBusy\", {\n method: \"POST\",\n body: JSON.stringify({\n timeMin: new Date(options.timeMin).toISOString(),\n timeMax: new Date(options.timeMax).toISOString(),\n items: [{ id: calendarId }],\n }),\n });\n\n return result.calendars[calendarId]?.busy ?? [];\n }\n\n async function findFreeSlots(options: {\n timeMin: Date | string;\n timeMax: Date | string;\n durationMinutes: number;\n calendarId?: string;\n }): Promise<Array<{ start: Date; end: Date }>> {\n const busySlots = await getFreeBusy({\n timeMin: options.timeMin,\n timeMax: options.timeMax,\n calendarId: options.calendarId,\n });\n\n const freeSlots: Array<{ start: Date; end: Date }> = [];\n const rangeStart = new Date(options.timeMin);\n const rangeEnd = new Date(options.timeMax);\n const durationMs = options.durationMinutes * 60 * 1000;\n\n let currentStart = rangeStart;\n\n const sortedBusy = busySlots\n .map((s) => ({ start: new Date(s.start), end: new Date(s.end) }))\n .sort((a, b) => a.start.getTime() - b.start.getTime());\n\n for (const busy of sortedBusy) {\n if (busy.start.getTime() - currentStart.getTime() >= durationMs) {\n freeSlots.push({ start: new Date(currentStart), end: new Date(busy.start) });\n }\n if (busy.end > currentStart) {\n currentStart = busy.end;\n }\n }\n\n if (rangeEnd.getTime() - currentStart.getTime() >= durationMs) {\n freeSlots.push({ start: new Date(currentStart), end: rangeEnd });\n }\n\n return freeSlots;\n }\n\n async function deleteEvent(eventId: string, calendarId = \"primary\"): Promise<void> {\n const accessToken = await getAccessToken();\n\n const response = await fetch(\n `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${eventId}`,\n {\n method: \"DELETE\",\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n },\n );\n\n if (!response.ok && response.status !== 204) {\n throw new Error(`Failed to delete event: ${response.status}`);\n }\n }\n\n return {\n listEvents,\n getTodayEvents,\n createEvent,\n getFreeBusy,\n findFreeSlots,\n deleteEvent,\n };\n}\n\nexport type CalendarClient = ReturnType<typeof createCalendarClient>;\n",
|
|
123
|
+
"tools/find-free-time.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createCalendarClient } from \"../../lib/calendar-client.ts\";\n\ntype FreeSlot = { start: Date; end: Date };\n\nexport default tool({\n id: \"find-free-time\",\n description: \"Find available time slots in the calendar for scheduling\",\n inputSchema: z.object({\n durationMinutes: z\n .number()\n .min(15)\n .max(480)\n .default(60)\n .describe(\"Duration needed in minutes\"),\n daysToSearch: z\n .number()\n .min(1)\n .max(14)\n .default(7)\n .describe(\"Number of days to search ahead\"),\n workingHoursOnly: z\n .boolean()\n .default(true)\n .describe(\"Only show slots during working hours (9 AM - 6 PM)\"),\n }),\n execute: async (\n { durationMinutes, daysToSearch, workingHoursOnly },\n context,\n ): Promise<unknown> => {\n // Default to \"current-user\" for development; in production, always pass userId from session\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const calendar = createCalendarClient(userId);\n\n const now = new Date();\n const searchEnd = new Date();\n searchEnd.setDate(searchEnd.getDate() + daysToSearch);\n\n const freeSlots = (await calendar.findFreeSlots({\n timeMin: now,\n timeMax: searchEnd,\n durationMinutes,\n })) as FreeSlot[];\n\n const slots = workingHoursOnly\n ? freeSlots.filter((slot) => {\n const startHour = slot.start.getHours();\n const endHour = slot.end.getHours();\n return startHour >= 9 && endHour <= 18;\n })\n : freeSlots;\n\n const formattedSlots = slots.slice(0, 10).map((slot) => {\n const duration = Math.round(\n (slot.end.getTime() - slot.start.getTime()) / (1000 * 60),\n );\n\n return {\n start: slot.start.toISOString(),\n end: slot.end.toISOString(),\n durationMinutes: duration,\n date: slot.start.toLocaleDateString(\"en-US\", {\n weekday: \"long\",\n month: \"short\",\n day: \"numeric\",\n }),\n timeRange: `${slot.start.toLocaleTimeString(\"en-US\", {\n hour: \"numeric\",\n minute: \"2-digit\",\n })} - ${slot.end.toLocaleTimeString(\"en-US\", {\n hour: \"numeric\",\n minute: \"2-digit\",\n })}`,\n };\n });\n\n const count = formattedSlots.length;\n\n return {\n freeSlots: formattedSlots,\n count,\n searchCriteria: {\n durationMinutes,\n daysToSearch,\n workingHoursOnly,\n },\n message:\n count > 0\n ? `Found ${count} available slot(s) of ${durationMinutes} minutes or more.`\n : `No free slots of ${durationMinutes} minutes found in the next ${daysToSearch} days.`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Calendar not connected. Please connect your Google Calendar.\",\n connectUrl: \"/api/auth/calendar\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
124
|
+
"tools/list-events.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createCalendarClient } from \"../../lib/calendar-client.ts\";\n\ntype CalendarEvent = {\n id: string;\n summary: string;\n description?: string;\n location?: string;\n start: { dateTime?: string; date?: string };\n end: { dateTime?: string; date?: string };\n status: string;\n htmlLink: string;\n attendees?: Array<{ email: string; displayName?: string; responseStatus?: string }>;\n};\n\nexport default tool({\n id: \"list-events\",\n description: \"List upcoming calendar events. By default shows events from now onwards.\",\n inputSchema: z.object({\n maxResults: z\n .number()\n .min(1)\n .max(100)\n .default(10)\n .describe(\"Maximum number of events to return\"),\n daysAhead: z\n .number()\n .min(1)\n .max(30)\n .default(7)\n .describe(\"Number of days to look ahead\"),\n todayOnly: z.boolean().default(false).describe(\"Only show events for today\"),\n }),\n execute: async ({ maxResults, daysAhead, todayOnly }, context) => {\n // Default to \"current-user\" for development; in production, always pass userId from session\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const calendar = createCalendarClient(userId);\n\n let events: CalendarEvent[];\n\n if (todayOnly) {\n events = (await calendar.getTodayEvents()) as CalendarEvent[];\n } else {\n const now = new Date();\n const futureDate = new Date();\n futureDate.setDate(futureDate.getDate() + daysAhead);\n\n events = (await calendar.listEvents({\n maxResults,\n timeMin: now,\n timeMax: futureDate,\n })) as CalendarEvent[];\n }\n\n return {\n events: events.map((event) => ({\n id: event.id,\n title: event.summary,\n description: event.description || null,\n location: event.location || null,\n start: event.start.dateTime || event.start.date,\n end: event.end.dateTime || event.end.date,\n isAllDay: !event.start.dateTime,\n status: event.status,\n url: event.htmlLink,\n attendees: event.attendees?.map((a) => ({\n email: a.email,\n name: a.displayName,\n status: a.responseStatus,\n })) ?? [],\n })),\n count: events.length,\n message: todayOnly\n ? `Found ${events.length} event(s) for today.`\n : `Found ${events.length} event(s) in the next ${daysAhead} days.`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Calendar not connected. Please connect your Google Calendar.\",\n connectUrl: \"/api/auth/calendar\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
125
|
+
"tools/create-event.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createCalendarClient } from \"../../lib/calendar-client.ts\";\n\nexport default tool({\n id: \"create-event\",\n description: \"Create a new event in Google Calendar\",\n inputSchema: z.object({\n title: z.string().min(1).describe(\"Event title\"),\n startTime: z\n .string()\n .describe(\"Start time in ISO 8601 format (e.g., '2024-01-15T09:00:00')\"),\n endTime: z\n .string()\n .describe(\"End time in ISO 8601 format (e.g., '2024-01-15T10:00:00')\"),\n description: z.string().optional().describe(\"Event description\"),\n location: z.string().optional().describe(\"Event location\"),\n attendees: z\n .array(z.string().email())\n .optional()\n .describe(\"Email addresses of attendees to invite\"),\n timeZone: z\n .string()\n .default(\"UTC\")\n .describe(\"Time zone for the event (e.g., 'America/New_York')\"),\n }),\n execute: async (\n { title, startTime, endTime, description, location, attendees, timeZone },\n context,\n ) => {\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const calendar = createCalendarClient(userId);\n\n const event = await calendar.createEvent({\n summary: title,\n start: startTime,\n end: endTime,\n description,\n location,\n attendees,\n timeZone,\n });\n\n return {\n success: true,\n event: {\n id: event.id,\n title: event.summary,\n start: event.start.dateTime || event.start.date,\n end: event.end.dateTime || event.end.date,\n url: event.htmlLink,\n location: event.location,\n attendees: event.attendees?.map((a: { email: string }) => a.email) ?? [],\n },\n message: `Event \"${title}\" created successfully.`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Calendar not connected. Please connect your Google Calendar.\",\n connectUrl: \"/api/auth/calendar\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
126
|
+
"app/api/auth/calendar/route.ts": "import { calendarConfig, createOAuthInitHandler, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(calendarConfig, { tokenStore: memoryTokenStore });\n",
|
|
127
|
+
"app/api/auth/calendar/callback/route.ts": "/**\n * Calendar OAuth Callback\n *\n * Handles the OAuth callback from Google and stores the tokens.\n */\n\nimport { calendarConfig, createOAuthCallbackHandler, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\n// Hybrid adapter: uses framework's memoryTokenStore for state (PKCE),\n// but user's tokenStore for actual token storage\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n // State methods - delegate to framework's memoryTokenStore (shared with init route)\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(calendarConfig, { tokenStore: hybridTokenStore });\n",
|
|
128
|
+
".env.example": "# =============================================================================\n# Google Calendar Integration Setup\n# =============================================================================\n#\n# STEP 1: Create a Google Cloud Project\n# Visit: https://console.cloud.google.com/projectcreate\n#\n# STEP 2: Enable the Google Calendar API\n# Visit: https://console.cloud.google.com/apis/library/calendar-json.googleapis.com\n# Click \"Enable\" to activate the Calendar API for your project\n#\n# STEP 3: Configure OAuth Consent Screen\n# Visit: https://console.cloud.google.com/apis/credentials/consent\n# - Choose \"External\" user type (or \"Internal\" for Workspace)\n# - Fill in app name, support email\n# - Add scopes: calendar.readonly, calendar.events\n# - Add your email as a test user (required for development)\n#\n# STEP 4: Create OAuth Credentials\n# Visit: https://console.cloud.google.com/apis/credentials\n# - Click \"Create Credentials\" > \"OAuth client ID\"\n# - Application type: \"Web application\"\n# - Add Authorized redirect URI: http://localhost:3000/api/auth/calendar/callback\n# - Copy the Client ID and Client Secret below\n#\n# =============================================================================\n\nGOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com\nGOOGLE_CLIENT_SECRET=your-client-secret\n"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"integration:box": {
|
|
132
|
+
"files": {
|
|
133
|
+
"lib/box-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst BOX_BASE_URL = \"https://api.box.com/2.0\";\nconst BOX_UPLOAD_URL = \"https://upload.box.com/api/2.0/files/content\";\n\ninterface BoxItemCollection<T> {\n total_count: number;\n entries: T[];\n offset: number;\n limit: number;\n}\n\ninterface BoxFile {\n id: string;\n type: \"file\";\n name: string;\n size: number;\n created_at: string;\n modified_at: string;\n description: string;\n path_collection: {\n entries: Array<{ id: string; name: string }>;\n };\n created_by: {\n id: string;\n name: string;\n };\n modified_by: {\n id: string;\n name: string;\n };\n shared_link?: {\n url: string;\n };\n}\n\ninterface BoxFolder {\n id: string;\n type: \"folder\";\n name: string;\n created_at: string;\n modified_at: string;\n description: string;\n path_collection: {\n entries: Array<{ id: string; name: string }>;\n };\n created_by: {\n id: string;\n name: string;\n };\n modified_by: {\n id: string;\n name: string;\n };\n item_collection?: {\n total_count: number;\n };\n}\n\ntype BoxItem = BoxFile | BoxFolder;\n\ninterface BoxSearchResult {\n type: \"file\" | \"folder\";\n id: string;\n name: string;\n size?: number;\n created_at: string;\n modified_at: string;\n path_collection: {\n entries: Array<{ id: string; name: string }>;\n };\n}\n\nasync function requireAccessToken(): Promise<string> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Box. Please connect your account.\");\n }\n return token;\n}\n\nasync function parseErrorMessage(\n response: Response,\n fallback: string,\n): Promise<string> {\n const error = await response.json().catch(() => ({} as { message?: string }));\n return `${fallback}: ${response.status} ${error.message || response.statusText}`;\n}\n\nasync function boxFetch<T>(\n endpoint: string,\n options: RequestInit = {},\n): Promise<T> {\n const token = await requireAccessToken();\n\n const response = await fetch(`${BOX_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n throw new Error(await parseErrorMessage(response, \"Box API error\"));\n }\n\n return response.json() as Promise<T>;\n}\n\nfunction toUploadBlob(fileContent: string | Buffer | Blob): Blob {\n if (typeof fileContent === \"string\") {\n return new Blob([fileContent], { type: \"text/plain\" });\n }\n\n if (fileContent instanceof Buffer) {\n return new Blob([fileContent]);\n }\n\n return fileContent;\n}\n\n/**\n * List files and folders in a Box folder\n */\nexport async function listFiles(options: {\n folderId?: string;\n limit?: number;\n offset?: number;\n}): Promise<BoxItem[]> {\n const { folderId = \"0\", limit = 100, offset = 0 } = options;\n\n const params = new URLSearchParams({\n limit: limit.toString(),\n offset: offset.toString(),\n fields:\n \"id,type,name,size,created_at,modified_at,description,path_collection,created_by,modified_by\",\n });\n\n const response = await boxFetch<BoxItemCollection<BoxItem>>(\n `/folders/${folderId}/items?${params}`,\n );\n\n return response.entries;\n}\n\n/**\n * Get details of a specific file or folder\n */\nexport async function getFile(\n itemId: string,\n itemType: \"file\" | \"folder\" = \"file\",\n): Promise<BoxItem> {\n const endpoint = itemType === \"file\"\n ? `/files/${itemId}`\n : `/folders/${itemId}`;\n\n const params = new URLSearchParams({\n fields:\n \"id,type,name,size,created_at,modified_at,description,path_collection,created_by,modified_by,shared_link\",\n });\n\n return boxFetch<BoxItem>(`${endpoint}?${params}`);\n}\n\n/**\n * Upload a file to Box\n */\nexport async function uploadFile(options: {\n parentFolderId: string;\n fileName: string;\n fileContent: string | Buffer | Blob;\n}): Promise<BoxFile> {\n const { parentFolderId, fileName, fileContent } = options;\n\n const token = await requireAccessToken();\n\n const formData = new FormData();\n formData.append(\n \"attributes\",\n JSON.stringify({\n name: fileName,\n parent: { id: parentFolderId },\n }),\n );\n formData.append(\"file\", toUploadBlob(fileContent), fileName);\n\n const response = await fetch(BOX_UPLOAD_URL, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n },\n body: formData,\n });\n\n if (!response.ok) {\n throw new Error(await parseErrorMessage(response, \"Box upload error\"));\n }\n\n const result = (await response.json()) as { entries: BoxFile[] };\n return result.entries[0];\n}\n\n/**\n * Download a file from Box\n */\nexport async function downloadFile(fileId: string): Promise<{\n content: ArrayBuffer;\n fileName: string;\n mimeType: string;\n}> {\n const token = await requireAccessToken();\n\n const fileInfo = (await getFile(fileId, \"file\")) as BoxFile;\n\n const response = await fetch(`${BOX_BASE_URL}/files/${fileId}/content`, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!response.ok) {\n throw new Error(await parseErrorMessage(response, \"Box download error\"));\n }\n\n const content = await response.arrayBuffer();\n const mimeType =\n response.headers.get(\"content-type\") ?? \"application/octet-stream\";\n\n return { content, fileName: fileInfo.name, mimeType };\n}\n\n/**\n * Create a new folder in Box\n */\nexport async function createFolder(options: {\n parentFolderId: string;\n name: string;\n}): Promise<BoxFolder> {\n const { parentFolderId, name } = options;\n\n return boxFetch<BoxFolder>(\"/folders\", {\n method: \"POST\",\n body: JSON.stringify({\n name,\n parent: { id: parentFolderId },\n }),\n });\n}\n\n/**\n * Search for files and folders in Box\n */\nexport async function searchFiles(options: {\n query: string;\n limit?: number;\n offset?: number;\n contentTypes?: string[];\n}): Promise<BoxSearchResult[]> {\n const { query, limit = 100, offset = 0, contentTypes } = options;\n\n const params = new URLSearchParams({\n query,\n limit: limit.toString(),\n offset: offset.toString(),\n fields: \"id,type,name,size,created_at,modified_at,path_collection\",\n });\n\n if (contentTypes?.length) {\n params.set(\"content_types\", contentTypes.join(\",\"));\n }\n\n const response = await boxFetch<BoxItemCollection<BoxSearchResult>>(\n `/search?${params}`,\n );\n\n return response.entries;\n}\n\n/**\n * Get current user info\n */\nexport async function getMe(): Promise<{ id: string; name: string; login: string }> {\n return boxFetch<{ id: string; name: string; login: string }>(\"/users/me\");\n}\n",
|
|
134
|
+
"tools/create-folder.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createFolder } from \"../../lib/box-client.ts\";\n\nexport default tool({\n id: \"create-folder\",\n description: \"Create a new folder in Box. Use '0' as parent folder ID to create in the root folder.\",\n inputSchema: z.object({\n parentFolderId: z.string().describe(\"The ID of the parent folder (use '0' for root folder)\"),\n name: z.string().describe(\"The name of the folder to create\"),\n }),\n async execute({ parentFolderId, name }) {\n const folder = await createFolder({ parentFolderId, name });\n const path = folder.path_collection?.entries.map((e) => e.name).join(\"/\") ?? \"/\";\n\n return {\n success: true,\n folder: {\n id: folder.id,\n name: folder.name,\n createdAt: folder.created_at,\n path,\n },\n };\n },\n});\n",
|
|
135
|
+
"tools/upload-file.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { uploadFile } from \"../../lib/box-client.ts\";\n\nexport default tool({\n id: \"upload-file\",\n description:\n \"Upload a file to Box. Provide the file content as a string. Use '0' as parent folder ID to upload to the root folder.\",\n inputSchema: z.object({\n parentFolderId: z\n .string()\n .describe(\"The ID of the parent folder to upload to (use '0' for root folder)\"),\n fileName: z\n .string()\n .describe(\"The name of the file including extension (e.g., 'document.txt')\"),\n fileContent: z.string().describe(\"The content of the file as a string\"),\n }),\n async execute({ parentFolderId, fileName, fileContent }) {\n const file = await uploadFile({ parentFolderId, fileName, fileContent });\n\n return {\n success: true,\n file: {\n id: file.id,\n name: file.name,\n size: file.size,\n createdAt: file.created_at,\n path: file.path_collection?.entries.map((e) => e.name).join(\"/\") ?? \"/\",\n },\n };\n },\n});\n",
|
|
136
|
+
"tools/get-file.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getFile } from \"../../lib/box-client.ts\";\n\nexport default tool({\n id: \"get-file\",\n description: \"Get detailed information about a specific file or folder in Box.\",\n inputSchema: z.object({\n itemId: z.string().describe(\"The ID of the file or folder\"),\n itemType: z\n .enum([\"file\", \"folder\"])\n .default(\"file\")\n .describe(\"Whether the item is a file or folder\"),\n }),\n async execute({ itemId, itemType }) {\n const item = await getFile(itemId, itemType);\n const isFile = item.type === \"file\";\n const path = item.path_collection?.entries.map((e) => e.name).join(\"/\") ?? \"/\";\n\n return {\n id: item.id,\n type: item.type,\n name: item.name,\n size: isFile ? item.size : undefined,\n description: item.description,\n createdAt: item.created_at,\n modifiedAt: item.modified_at,\n createdBy: item.created_by?.name,\n modifiedBy: item.modified_by?.name,\n path,\n sharedLink: isFile ? item.shared_link?.url : undefined,\n };\n },\n});\n",
|
|
137
|
+
"tools/search-files.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { searchFiles } from \"../../lib/box-client.ts\";\n\nexport default tool({\n id: \"search-files\",\n description:\n \"Search for files and folders in Box by name or content. Returns matching items with their details.\",\n inputSchema: z.object({\n query: z.string().describe(\"Search query string to find files and folders\"),\n limit: z.number().min(1).max(100).default(50).describe(\"Maximum number of results to return\"),\n offset: z.number().min(0).default(0).describe(\"Number of results to skip for pagination\"),\n contentTypes: z\n .array(z.string())\n .optional()\n .describe(\"Filter by content types (e.g., ['name', 'description', 'file_content'])\"),\n }),\n async execute({ query, limit, offset, contentTypes }) {\n const results = await searchFiles({ query, limit, offset, contentTypes });\n\n return results.map((item) => ({\n id: item.id,\n type: item.type,\n name: item.name,\n size: item.size,\n createdAt: item.created_at,\n modifiedAt: item.modified_at,\n path: item.path_collection?.entries.map((e) => e.name).join(\"/\") ?? \"/\",\n }));\n },\n});\n",
|
|
138
|
+
"tools/list-files.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listFiles } from \"../../lib/box-client.ts\";\n\nexport default tool({\n id: \"list-files\",\n description:\n \"List files and folders from a Box folder. Use folder ID '0' for the root folder.\",\n inputSchema: z.object({\n folderId: z\n .string()\n .default(\"0\")\n .describe(\"Folder ID to list files from (use '0' for root folder)\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(50)\n .describe(\"Maximum number of items to return\"),\n offset: z\n .number()\n .min(0)\n .default(0)\n .describe(\"Number of items to skip for pagination\"),\n }),\n async execute({ folderId, limit, offset }) {\n const items = await listFiles({ folderId, limit, offset });\n\n return items.map((item) => ({\n id: item.id,\n type: item.type,\n name: item.name,\n size: item.type === \"file\" ? item.size : undefined,\n createdAt: item.created_at,\n modifiedAt: item.modified_at,\n createdBy: item.created_by?.name,\n modifiedBy: item.modified_by?.name,\n path: item.path_collection?.entries.map((e) => e.name).join(\"/\") ?? \"/\",\n }));\n },\n});\n",
|
|
139
|
+
"app/api/auth/box/route.ts": "import { boxConfig, createOAuthInitHandler } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(boxConfig);\n",
|
|
140
|
+
"app/api/auth/box/callback/route.ts": "import { boxConfig, createOAuthCallbackHandler, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(boxConfig, { tokenStore: hybridTokenStore });\n",
|
|
141
|
+
".env.example": "# Box OAuth Configuration\n# Get your credentials from https://app.box.com/developers/console\nBOX_CLIENT_ID=your-client-id\nBOX_CLIENT_SECRET=your-client-secret\n"
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"integration:aws": {
|
|
145
|
+
"files": {
|
|
146
|
+
"lib/aws-client.ts": "import { S3Client, ListBucketsCommand, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';\nimport { EC2Client, DescribeInstancesCommand } from '@aws-sdk/client-ec2';\nimport { LambdaClient, ListFunctionsCommand } from '@aws-sdk/client-lambda';\n\ninterface AWSClientConfig {\n region?: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n}\n\nexport interface S3Bucket {\n name: string;\n creationDate?: Date;\n}\n\nexport interface S3Object {\n key: string;\n size: number;\n lastModified?: Date;\n etag?: string;\n storageClass?: string;\n}\n\nexport interface EC2Instance {\n instanceId: string;\n instanceType: string;\n state: string;\n publicIpAddress?: string;\n privateIpAddress?: string;\n launchTime?: Date;\n name?: string;\n availabilityZone?: string;\n}\n\nexport interface LambdaFunction {\n functionName: string;\n functionArn: string;\n runtime?: string;\n handler?: string;\n codeSize: number;\n lastModified: string;\n memorySize?: number;\n timeout?: number;\n description?: string;\n}\n\nexport class AWSClient {\n private region: string;\n private credentials: { accessKeyId: string; secretAccessKey: string };\n\n constructor(config?: AWSClientConfig) {\n this.region = config?.region ?? process.env.AWS_REGION ?? 'us-east-1';\n this.credentials = {\n accessKeyId: config?.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID ?? '',\n secretAccessKey: config?.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY ?? '',\n };\n\n if (!this.credentials.accessKeyId || !this.credentials.secretAccessKey) {\n throw new Error(\n 'AWS credentials are required. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.',\n );\n }\n }\n\n private getS3Client(): S3Client {\n return new S3Client({ region: this.region, credentials: this.credentials });\n }\n\n private getEC2Client(region?: string): EC2Client {\n return new EC2Client({ region: region ?? this.region, credentials: this.credentials });\n }\n\n private getLambdaClient(region?: string): LambdaClient {\n return new LambdaClient({ region: region ?? this.region, credentials: this.credentials });\n }\n\n async listS3Buckets(): Promise<S3Bucket[]> {\n const client = this.getS3Client();\n\n try {\n const response = await client.send(new ListBucketsCommand({}));\n return (response.Buckets ?? []).map(bucket => ({\n name: bucket.Name ?? '',\n creationDate: bucket.CreationDate,\n }));\n } catch (error) {\n throw new Error(`Failed to list S3 buckets: ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n }\n\n async listS3Objects(bucket: string, prefix?: string, maxKeys?: number): Promise<S3Object[]> {\n const client = this.getS3Client();\n\n try {\n const response = await client.send(\n new ListObjectsV2Command({\n Bucket: bucket,\n Prefix: prefix,\n MaxKeys: maxKeys ?? 1000,\n }),\n );\n\n return (response.Contents ?? []).map(object => ({\n key: object.Key ?? '',\n size: object.Size ?? 0,\n lastModified: object.LastModified,\n etag: object.ETag,\n storageClass: object.StorageClass,\n }));\n } catch (error) {\n throw new Error(\n `Failed to list S3 objects in bucket ${bucket}: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n }\n\n async getS3Object(bucket: string, key: string): Promise<string> {\n const client = this.getS3Client();\n\n try {\n const response = await client.send(\n new GetObjectCommand({\n Bucket: bucket,\n Key: key,\n }),\n );\n\n if (!response.Body) {\n throw new Error('Object body is empty');\n }\n\n return await response.Body.transformToString();\n } catch (error) {\n throw new Error(\n `Failed to get S3 object ${key} from bucket ${bucket}: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n }\n\n async listEC2Instances(region?: string): Promise<EC2Instance[]> {\n const client = this.getEC2Client(region);\n\n try {\n const response = await client.send(new DescribeInstancesCommand({}));\n\n return (response.Reservations ?? []).flatMap(reservation =>\n (reservation.Instances ?? []).map(instance => {\n const nameTag = instance.Tags?.find(tag => tag.Key === 'Name');\n\n return {\n instanceId: instance.InstanceId ?? '',\n instanceType: instance.InstanceType ?? '',\n state: instance.State?.Name ?? 'unknown',\n publicIpAddress: instance.PublicIpAddress,\n privateIpAddress: instance.PrivateIpAddress,\n launchTime: instance.LaunchTime,\n name: nameTag?.Value,\n availabilityZone: instance.Placement?.AvailabilityZone,\n };\n }),\n );\n } catch (error) {\n throw new Error(`Failed to list EC2 instances: ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n }\n\n async listLambdaFunctions(region?: string): Promise<LambdaFunction[]> {\n const client = this.getLambdaClient(region);\n\n try {\n const response = await client.send(new ListFunctionsCommand({}));\n\n return (response.Functions ?? []).map(func => ({\n functionName: func.FunctionName ?? '',\n functionArn: func.FunctionArn ?? '',\n runtime: func.Runtime,\n handler: func.Handler,\n codeSize: func.CodeSize ?? 0,\n lastModified: func.LastModified ?? '',\n memorySize: func.MemorySize,\n timeout: func.Timeout,\n description: func.Description,\n }));\n } catch (error) {\n throw new Error(`Failed to list Lambda functions: ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n }\n}\n\nlet awsClient: AWSClient | null = null;\n\nexport function getAWSClient(config?: AWSClientConfig): AWSClient {\n if (awsClient) return awsClient;\n awsClient = new AWSClient(config);\n return awsClient;\n}\n\nexport default AWSClient;\n",
|
|
147
|
+
"tools/list-s3-objects.ts": "import { tool } from 'veryfront/tool';\nimport { z } from 'zod';\nimport { getAWSClient } from '../../lib/aws-client';\n\nexport const listS3ObjectsTool = tool({\n name: 'list-s3-objects',\n description: 'List objects in a specific S3 bucket. Optionally filter by prefix and limit the number of results.',\n input: z.object({\n bucket: z.string().describe('The name of the S3 bucket to list objects from'),\n prefix: z.string().optional().describe('Optional prefix to filter objects (e.g., \"folder/\" or \"images/\")'),\n maxKeys: z.number().min(1).max(1000).optional().describe('Maximum number of objects to return (default: 1000)'),\n }),\n execute: async ({ bucket, prefix, maxKeys }) => {\n try {\n const client = getAWSClient();\n const objects = await client.listS3Objects(bucket, prefix, maxKeys);\n const prefixMessage = prefix ? ` with prefix \"${prefix}\"` : '';\n\n if (objects.length === 0) {\n return {\n success: true,\n message: `No objects found in bucket \"${bucket}\"${prefixMessage}.`,\n objects: [],\n bucket,\n prefix,\n };\n }\n\n const count = objects.length;\n\n return {\n success: true,\n message: `Found ${count} object${count === 1 ? '' : 's'} in bucket \"${bucket}\"${prefixMessage}.`,\n objects: objects.map((obj) => ({\n key: obj.key,\n size: obj.size,\n lastModified: obj.lastModified?.toISOString(),\n etag: obj.etag,\n storageClass: obj.storageClass,\n })),\n count,\n bucket,\n prefix,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to list S3 objects',\n objects: [],\n bucket,\n prefix,\n };\n }\n },\n});\n\nexport default listS3ObjectsTool;\n",
|
|
148
|
+
"tools/get-s3-object.ts": "import { tool } from 'veryfront/tool';\nimport { z } from 'zod';\nimport { getAWSClient } from '../../lib/aws-client';\n\nexport const getS3ObjectTool = tool({\n name: 'get-s3-object',\n description: 'Get the contents of an object from an S3 bucket. Returns the object content as a string.',\n input: z.object({\n bucket: z.string().describe('The name of the S3 bucket'),\n key: z.string().describe('The key (path) of the object to retrieve'),\n }),\n execute: async ({ bucket, key }) => {\n try {\n const client = getAWSClient();\n const content = await client.getS3Object(bucket, key);\n\n const isBinary = /[\\x00-\\x08\\x0E-\\x1F]/.test(content.substring(0, 8000));\n if (isBinary) {\n return {\n success: true,\n message: `Retrieved object \"${key}\" from bucket \"${bucket}\". Content appears to be binary.`,\n bucket,\n key,\n contentType: 'binary',\n contentLength: content.length,\n contentPreview: '[Binary content - not displayed]',\n };\n }\n\n const maxPreviewLength = 10000;\n const truncated = content.length > maxPreviewLength;\n const contentPreview = truncated ? `${content.substring(0, maxPreviewLength)}\\n... [truncated]` : content;\n\n return {\n success: true,\n message: `Retrieved object \"${key}\" from bucket \"${bucket}\".`,\n bucket,\n key,\n contentType: 'text',\n contentLength: content.length,\n content: contentPreview,\n truncated,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to get S3 object',\n bucket,\n key,\n };\n }\n },\n});\n\nexport default getS3ObjectTool;\n",
|
|
149
|
+
"tools/list-lambda-functions.ts": "import { tool } from 'veryfront/tool';\nimport { z } from 'zod';\nimport { getAWSClient } from '../../lib/aws-client';\n\nexport const listLambdaFunctionsTool = tool({\n name: 'list-lambda-functions',\n description:\n 'List all Lambda functions in your AWS account. Returns function details including name, ARN, runtime, and configuration.',\n input: z.object({\n region: z\n .string()\n .optional()\n .describe(\n 'AWS region to list Lambda functions from (e.g., \"us-east-1\", \"eu-west-1\"). Defaults to configured region.',\n ),\n }),\n execute: async ({ region }) => {\n try {\n const client = getAWSClient();\n const functions = await client.listLambdaFunctions(region);\n const regionMessage = region ? ` in region \"${region}\"` : '';\n\n if (functions.length === 0) {\n return {\n success: true,\n message: `No Lambda functions found${regionMessage}.`,\n functions: [],\n region,\n };\n }\n\n return {\n success: true,\n message: `Found ${functions.length} Lambda function${functions.length === 1 ? '' : 's'}${regionMessage}.`,\n functions: functions.map((func) => ({\n functionName: func.functionName,\n functionArn: func.functionArn,\n runtime: func.runtime ?? 'N/A',\n handler: func.handler ?? 'N/A',\n codeSize: func.codeSize,\n lastModified: func.lastModified,\n memorySize: func.memorySize ?? 'N/A',\n timeout: func.timeout ?? 'N/A',\n description: func.description ?? 'No description',\n })),\n count: functions.length,\n region,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to list Lambda functions',\n functions: [],\n region,\n };\n }\n },\n});\n\nexport default listLambdaFunctionsTool;\n",
|
|
150
|
+
"tools/list-ec2-instances.ts": "import { tool } from 'veryfront/tool';\nimport { z } from 'zod';\nimport { getAWSClient } from '../../lib/aws-client';\n\nexport const listEC2InstancesTool = tool({\n name: 'list-ec2-instances',\n description:\n 'List all EC2 instances in your AWS account. Returns instance details including ID, type, state, and IP addresses.',\n input: z.object({\n region: z\n .string()\n .optional()\n .describe(\n 'AWS region to list instances from (e.g., \"us-east-1\", \"eu-west-1\"). Defaults to configured region.',\n ),\n }),\n execute: async ({ region }) => {\n try {\n const client = getAWSClient();\n const instances = await client.listEC2Instances(region);\n const regionMessage = region ? ` in region \"${region}\"` : '';\n\n if (instances.length === 0) {\n return {\n success: true,\n message: `No EC2 instances found${regionMessage}.`,\n instances: [],\n region,\n };\n }\n\n return {\n success: true,\n message: `Found ${instances.length} EC2 instance${instances.length === 1 ? '' : 's'}${regionMessage}.`,\n instances: instances.map((instance) => ({\n instanceId: instance.instanceId,\n instanceType: instance.instanceType,\n state: instance.state,\n name: instance.name ?? 'N/A',\n publicIpAddress: instance.publicIpAddress ?? 'N/A',\n privateIpAddress: instance.privateIpAddress ?? 'N/A',\n availabilityZone: instance.availabilityZone ?? 'N/A',\n launchTime: instance.launchTime?.toISOString(),\n })),\n count: instances.length,\n region,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to list EC2 instances',\n instances: [],\n region,\n };\n }\n },\n});\n\nexport default listEC2InstancesTool;\n",
|
|
151
|
+
"tools/list-s3-buckets.ts": "import { tool } from 'veryfront/tool';\nimport { z } from 'zod';\nimport { getAWSClient } from '../../lib/aws-client';\n\nexport const listS3BucketsTool = tool({\n name: 'list-s3-buckets',\n description: 'List all S3 buckets in your AWS account. Returns bucket names and creation dates.',\n input: z.object({}),\n execute: async () => {\n try {\n const client = getAWSClient();\n const buckets = await client.listS3Buckets();\n const count = buckets.length;\n\n if (count === 0) {\n return {\n success: true,\n message: 'No S3 buckets found in your AWS account.',\n buckets: [],\n };\n }\n\n return {\n success: true,\n message: `Found ${count} S3 bucket${count === 1 ? '' : 's'}.`,\n buckets: buckets.map((bucket) => ({\n name: bucket.name,\n creationDate: bucket.creationDate?.toISOString(),\n })),\n count,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to list S3 buckets',\n buckets: [],\n };\n }\n },\n});\n\nexport default listS3BucketsTool;\n",
|
|
152
|
+
".env.example": "# AWS Integration Configuration\n\n# AWS Access Key ID (from IAM user)\nAWS_ACCESS_KEY_ID=your_access_key_id_here\n\n# AWS Secret Access Key (from IAM user)\nAWS_SECRET_ACCESS_KEY=your_secret_access_key_here\n\n# AWS Region (e.g., us-east-1, us-west-2, eu-west-1)\nAWS_REGION=us-east-1\n"
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"integration:teams": {
|
|
156
|
+
"files": {
|
|
157
|
+
"lib/teams-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst GRAPH_API_BASE = \"https://graph.microsoft.com/v1.0\";\n\ninterface GraphResponse<T> {\n \"@odata.context\"?: string;\n \"@odata.nextLink\"?: string;\n value?: T[];\n}\n\nexport interface TeamsChat {\n id: string;\n topic: string | null;\n createdDateTime: string;\n lastUpdatedDateTime: string;\n chatType: \"oneOnOne\" | \"group\" | \"meeting\";\n webUrl?: string;\n members?: ChatMember[];\n}\n\nexport interface ChatMember {\n \"@odata.type\": string;\n id: string;\n displayName?: string;\n userId?: string;\n email?: string;\n}\n\nexport interface ChatMessage {\n id: string;\n messageType: \"message\" | \"chatEvent\" | \"typing\";\n createdDateTime: string;\n lastModifiedDateTime?: string;\n deletedDateTime?: string;\n subject?: string | null;\n summary?: string | null;\n importance: \"normal\" | \"high\" | \"urgent\";\n locale?: string;\n from: {\n user?: {\n id: string;\n displayName?: string;\n userIdentityType?: string;\n };\n };\n body: {\n contentType: \"text\" | \"html\";\n content: string;\n };\n attachments?: Array<{\n id: string;\n contentType: string;\n contentUrl?: string;\n content?: string;\n name?: string;\n }>;\n mentions?: Array<{\n id: number;\n mentionText: string;\n mentioned: {\n user: {\n id: string;\n displayName?: string;\n };\n };\n }>;\n reactions?: Array<{\n reactionType: string;\n createdDateTime: string;\n user: {\n id: string;\n displayName?: string;\n };\n }>;\n}\n\nexport interface Team {\n id: string;\n displayName: string;\n description?: string;\n createdDateTime?: string;\n webUrl?: string;\n isArchived?: boolean;\n visibility?: \"private\" | \"public\";\n}\n\nexport interface Channel {\n id: string;\n displayName: string;\n description?: string;\n email?: string;\n webUrl?: string;\n membershipType?: \"standard\" | \"private\" | \"shared\";\n createdDateTime?: string;\n}\n\nfunction buildEndpoint(path: string, params?: URLSearchParams): string {\n const queryString = params?.toString();\n return `${path}${queryString ? `?${queryString}` : \"\"}`;\n}\n\nasync function graphFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Microsoft Teams. Please connect your account.\");\n }\n\n const url = endpoint.startsWith(\"http\") ? endpoint : `${GRAPH_API_BASE}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as any));\n throw new Error(\n `Microsoft Graph API error: ${response.status} ${error?.error?.message ?? response.statusText}`,\n );\n }\n\n return response.json();\n}\n\nexport async function listChats(options?: { limit?: number; expand?: string[] }): Promise<TeamsChat[]> {\n const params = new URLSearchParams();\n if (options?.limit) params.set(\"$top\", options.limit.toString());\n if (options?.expand?.length) params.set(\"$expand\", options.expand.join(\",\"));\n\n const response = await graphFetch<GraphResponse<TeamsChat>>(buildEndpoint(\"/me/chats\", params));\n return response.value ?? [];\n}\n\nexport async function getChatMessages(\n chatId: string,\n options?: { limit?: number; orderBy?: string },\n): Promise<ChatMessage[]> {\n const params = new URLSearchParams();\n if (options?.limit) params.set(\"$top\", options.limit.toString());\n params.set(\"$orderby\", options?.orderBy ?? \"createdDateTime desc\");\n\n const response = await graphFetch<GraphResponse<ChatMessage>>(\n buildEndpoint(`/me/chats/${chatId}/messages`, params),\n );\n return response.value ?? [];\n}\n\nexport function sendChatMessage(\n chatId: string,\n content: string,\n contentType: \"text\" | \"html\" = \"text\",\n): Promise<ChatMessage> {\n return graphFetch<ChatMessage>(`/me/chats/${chatId}/messages`, {\n method: \"POST\",\n body: JSON.stringify({\n body: { contentType, content },\n }),\n });\n}\n\nexport async function listTeams(options?: { limit?: number }): Promise<Team[]> {\n const params = new URLSearchParams();\n if (options?.limit) params.set(\"$top\", options.limit.toString());\n\n const response = await graphFetch<GraphResponse<Team>>(buildEndpoint(\"/me/joinedTeams\", params));\n return response.value ?? [];\n}\n\nexport async function listChannels(\n teamId: string,\n options?: { limit?: number },\n): Promise<Channel[]> {\n const params = new URLSearchParams();\n if (options?.limit) params.set(\"$top\", options.limit.toString());\n\n const response = await graphFetch<GraphResponse<Channel>>(\n buildEndpoint(`/teams/${teamId}/channels`, params),\n );\n return response.value ?? [];\n}\n\nexport function sendChannelMessage(\n teamId: string,\n channelId: string,\n content: string,\n contentType: \"text\" | \"html\" = \"text\",\n subject?: string,\n): Promise<ChatMessage> {\n const body: Record<string, unknown> = {\n body: { contentType, content },\n ...(subject ? { subject } : {}),\n };\n\n return graphFetch<ChatMessage>(`/teams/${teamId}/channels/${channelId}/messages`, {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport async function getChannelMessages(\n teamId: string,\n channelId: string,\n options?: { limit?: number; orderBy?: string },\n): Promise<ChatMessage[]> {\n const params = new URLSearchParams();\n if (options?.limit) params.set(\"$top\", options.limit.toString());\n params.set(\"$orderby\", options?.orderBy ?? \"createdDateTime desc\");\n\n const response = await graphFetch<GraphResponse<ChatMessage>>(\n buildEndpoint(`/teams/${teamId}/channels/${channelId}/messages`, params),\n );\n return response.value ?? [];\n}\n\nexport function getCurrentUser(): Promise<{\n id: string;\n displayName: string;\n mail?: string;\n userPrincipalName?: string;\n}> {\n return graphFetch(\"/me\");\n}\n\nexport function getChatDisplayName(chat: TeamsChat): string {\n if (chat.topic) return chat.topic;\n\n const memberNames = chat.members\n ?.map((m) => m.displayName)\n .filter(Boolean)\n .join(\", \");\n\n if (memberNames) return memberNames;\n\n return chat.chatType === \"oneOnOne\" ? \"Direct Chat\" : \"Group Chat\";\n}\n\nexport function getPlainTextContent(message: ChatMessage): string {\n if (message.body.contentType === \"text\") return message.body.content;\n\n return message.body.content\n .replace(/<[^>]*>/g, \"\")\n .replace(/ /g, \" \")\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .trim();\n}\n",
|
|
158
|
+
"tools/list-chats.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getChatDisplayName, listChats } from \"../../lib/teams-client.ts\";\n\nexport default tool({\n id: \"list-chats\",\n description:\n \"List recent Microsoft Teams chats for the authenticated user. Returns chat IDs, names, types, and last updated timestamps.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(50)\n .default(20)\n .describe(\"Maximum number of chats to return (1-50)\"),\n expandMembers: z\n .boolean()\n .default(false)\n .describe(\"Include chat member information\"),\n }),\n async execute({ limit, expandMembers }) {\n const chats = await listChats({\n limit,\n expand: expandMembers ? [\"members\"] : undefined,\n });\n\n return chats.map((chat) => {\n const members = expandMembers\n ? chat.members?.map((m) => ({\n id: m.id,\n displayName: m.displayName,\n email: m.email,\n }))\n : undefined;\n\n return {\n id: chat.id,\n name: getChatDisplayName(chat),\n type: chat.chatType,\n topic: chat.topic,\n lastUpdated: chat.lastUpdatedDateTime,\n created: chat.createdDateTime,\n webUrl: chat.webUrl,\n memberCount: chat.members?.length,\n members,\n };\n });\n },\n});\n",
|
|
159
|
+
"tools/send-message.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { sendChannelMessage, sendChatMessage } from \"../../lib/teams-client.ts\";\n\nexport default tool({\n id: \"send-message\",\n description:\n \"Send a message to a Microsoft Teams chat or channel. For chats, use the chatId. For channels, use both teamId and channelId.\",\n inputSchema: z\n .object({\n chatId: z\n .string()\n .optional()\n .describe(\"The ID of the chat to send the message to (use this for direct/group chats)\"),\n teamId: z\n .string()\n .optional()\n .describe(\"The ID of the team (use with channelId for channel messages)\"),\n channelId: z\n .string()\n .optional()\n .describe(\"The ID of the channel (use with teamId for channel messages)\"),\n content: z.string().min(1).describe(\"The message content to send\"),\n contentType: z.enum([\"text\", \"html\"]).default(\"text\").describe(\"Content format: text or html\"),\n subject: z.string().optional().describe(\"Subject line (only for channel messages)\"),\n })\n .refine(\n (data) =>\n (data.chatId && !data.teamId && !data.channelId) ||\n (!data.chatId && data.teamId && data.channelId),\n { message: \"Either provide chatId OR both teamId and channelId\" },\n ),\n async execute({ chatId, teamId, channelId, content, contentType, subject }) {\n if (chatId) {\n const message = await sendChatMessage(chatId, content, contentType);\n return {\n success: true,\n messageId: message.id,\n type: \"chat\",\n chatId,\n createdAt: message.createdDateTime,\n content: message.body.content,\n };\n }\n\n if (!teamId || !channelId) {\n throw new Error(\"Invalid parameters: provide either chatId or both teamId and channelId\");\n }\n\n const message = await sendChannelMessage(teamId, channelId, content, contentType, subject);\n return {\n success: true,\n messageId: message.id,\n type: \"channel\",\n teamId,\n channelId,\n subject,\n createdAt: message.createdDateTime,\n content: message.body.content,\n };\n },\n});\n",
|
|
160
|
+
"tools/get-messages.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getChatMessages, getPlainTextContent } from \"../../lib/teams-client.ts\";\n\nexport default tool({\n id: \"get-messages\",\n description:\n \"Get messages from a specific Microsoft Teams chat. Returns message content, sender information, and timestamps. Use list-chats first to get chat IDs.\",\n inputSchema: z.object({\n chatId: z.string().describe(\"The ID of the chat to get messages from\"),\n limit: z\n .number()\n .min(1)\n .max(50)\n .default(20)\n .describe(\"Maximum number of messages to return (1-50)\"),\n includeHtml: z\n .boolean()\n .default(false)\n .describe(\"Include HTML formatted content in addition to plain text\"),\n }),\n async execute({ chatId, limit, includeHtml }) {\n const messages = await getChatMessages(chatId, {\n limit,\n orderBy: \"createdDateTime desc\",\n });\n\n return messages\n .filter((msg) => msg.messageType === \"message\")\n .map((msg) => ({\n id: msg.id,\n content: getPlainTextContent(msg),\n htmlContent: includeHtml ? msg.body.content : undefined,\n contentType: msg.body.contentType,\n sender: {\n id: msg.from.user?.id,\n displayName: msg.from.user?.displayName,\n },\n createdAt: msg.createdDateTime,\n lastModified: msg.lastModifiedDateTime,\n importance: msg.importance,\n subject: msg.subject,\n hasAttachments: (msg.attachments?.length ?? 0) > 0,\n attachmentCount: msg.attachments?.length ?? 0,\n attachments: msg.attachments?.map((att) => ({\n id: att.id,\n name: att.name,\n contentType: att.contentType,\n contentUrl: att.contentUrl,\n })),\n mentions: msg.mentions?.map((mention) => ({\n text: mention.mentionText,\n userId: mention.mentioned.user.id,\n displayName: mention.mentioned.user.displayName,\n })),\n reactionCount: msg.reactions?.length ?? 0,\n }));\n },\n});\n",
|
|
161
|
+
"tools/list-channels.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listChannels } from \"../../lib/teams-client.ts\";\n\nexport default tool({\n id: \"list-channels\",\n description:\n \"List all channels in a specific Microsoft Team. Use list-teams first to get team IDs. Returns channel IDs, names, descriptions, and types.\",\n inputSchema: z.object({\n teamId: z.string().describe(\"The ID of the team to list channels from\"),\n limit: z\n .number()\n .min(1)\n .max(50)\n .default(25)\n .describe(\"Maximum number of channels to return (1-50)\"),\n }),\n async execute({ teamId, limit }) {\n const channels = await listChannels(teamId, { limit });\n\n return channels.map(\n ({\n id,\n displayName,\n description,\n email,\n webUrl,\n membershipType,\n createdDateTime,\n }) => ({\n id,\n name: displayName,\n description,\n email,\n webUrl,\n membershipType,\n createdAt: createdDateTime,\n }),\n );\n },\n});\n",
|
|
162
|
+
"tools/list-teams.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listTeams } from \"../../lib/teams-client.ts\";\n\nexport default tool({\n id: \"list-teams\",\n description:\n \"List all Microsoft Teams that the authenticated user is a member of. Returns team IDs, names, descriptions, and metadata.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(50)\n .default(25)\n .describe(\"Maximum number of teams to return (1-50)\"),\n }),\n async execute({ limit }) {\n const teams = await listTeams({ limit });\n\n return teams.map(\n ({\n id,\n displayName,\n description,\n visibility,\n isArchived,\n createdDateTime,\n webUrl,\n }) => ({\n id,\n name: displayName,\n description,\n visibility,\n isArchived,\n createdAt: createdDateTime,\n webUrl,\n }),\n );\n },\n});\n",
|
|
163
|
+
"app/api/auth/teams/route.ts": "import { createOAuthInitHandler, memoryTokenStore, teamsConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(teamsConfig, { tokenStore: memoryTokenStore });\n",
|
|
164
|
+
"app/api/auth/teams/callback/route.ts": "/**\n * Teams OAuth Callback\n *\n * Handles the OAuth callback from Microsoft and stores the tokens.\n */\n\nimport { createOAuthCallbackHandler, memoryTokenStore, teamsConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\n// Hybrid adapter: uses framework's memoryTokenStore for state (PKCE),\n// but user's tokenStore for actual token storage\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(teamsConfig, { tokenStore: hybridTokenStore });\n"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
"integration:gitlab": {
|
|
168
|
+
"files": {
|
|
169
|
+
"lib/gitlab-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst GITLAB_BASE_URL = \"https://gitlab.com/api/v4\";\n\nexport interface GitLabProject {\n id: number;\n name: string;\n name_with_namespace: string;\n description: string | null;\n web_url: string;\n path_with_namespace: string;\n default_branch: string;\n visibility: \"private\" | \"internal\" | \"public\";\n created_at: string;\n last_activity_at: string;\n}\n\nexport interface GitLabIssue {\n id: number;\n iid: number;\n project_id: number;\n title: string;\n description: string | null;\n state: \"opened\" | \"closed\";\n created_at: string;\n updated_at: string;\n closed_at: string | null;\n labels: string[];\n milestone: {\n id: number;\n title: string;\n } | null;\n assignees: Array<{\n id: number;\n username: string;\n name: string;\n avatar_url: string;\n }>;\n author: {\n id: number;\n username: string;\n name: string;\n avatar_url: string;\n };\n web_url: string;\n time_stats: {\n time_estimate: number;\n total_time_spent: number;\n };\n}\n\nexport interface GitLabMergeRequest {\n id: number;\n iid: number;\n project_id: number;\n title: string;\n description: string | null;\n state: \"opened\" | \"closed\" | \"merged\";\n created_at: string;\n updated_at: string;\n merged_at: string | null;\n closed_at: string | null;\n target_branch: string;\n source_branch: string;\n author: {\n id: number;\n username: string;\n name: string;\n avatar_url: string;\n };\n assignees: Array<{\n id: number;\n username: string;\n name: string;\n avatar_url: string;\n }>;\n reviewers: Array<{\n id: number;\n username: string;\n name: string;\n avatar_url: string;\n }>;\n labels: string[];\n draft: boolean;\n web_url: string;\n changes_count: string;\n diff_refs: {\n base_sha: string;\n head_sha: string;\n start_sha: string;\n };\n}\n\nexport interface GitLabUser {\n id: number;\n username: string;\n name: string;\n email: string;\n avatar_url: string;\n web_url: string;\n}\n\nfunction encodeProjectId(projectId: number | string): number | string {\n return typeof projectId === \"string\" ? encodeURIComponent(projectId) : projectId;\n}\n\nfunction buildQuery(params: URLSearchParams): string {\n const query = params.toString();\n return query ? `?${query}` : \"\";\n}\n\nasync function gitlabFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with GitLab. Please connect your account.\");\n }\n\n const response = await fetch(`${GITLAB_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as Record<string, unknown>));\n const message =\n (error as { message?: string; error?: string }).message ??\n (error as { message?: string; error?: string }).error ??\n response.statusText;\n\n throw new Error(`GitLab API error: ${response.status} ${message}`);\n }\n\n return response.json() as Promise<T>;\n}\n\nexport function getCurrentUser(): Promise<GitLabUser> {\n return gitlabFetch<GitLabUser>(\"/user\");\n}\n\nexport function listProjects(options?: {\n membership?: boolean;\n search?: string;\n orderBy?: \"id\" | \"name\" | \"created_at\" | \"updated_at\" | \"last_activity_at\";\n sort?: \"asc\" | \"desc\";\n perPage?: number;\n}): Promise<GitLabProject[]> {\n const params = new URLSearchParams();\n\n if (options?.membership !== false) params.set(\"membership\", \"true\");\n if (options?.search) params.set(\"search\", options.search);\n if (options?.orderBy) params.set(\"order_by\", options.orderBy);\n if (options?.sort) params.set(\"sort\", options.sort);\n if (options?.perPage) params.set(\"per_page\", options.perPage.toString());\n\n return gitlabFetch<GitLabProject[]>(`/projects${buildQuery(params)}`);\n}\n\nexport function getProject(projectId: number | string): Promise<GitLabProject> {\n return gitlabFetch<GitLabProject>(`/projects/${encodeProjectId(projectId)}`);\n}\n\nexport function searchIssues(options: {\n scope?: \"created_by_me\" | \"assigned_to_me\" | \"all\";\n state?: \"opened\" | \"closed\" | \"all\";\n labels?: string[];\n search?: string;\n projectId?: number | string;\n perPage?: number;\n}): Promise<GitLabIssue[]> {\n const params = new URLSearchParams();\n\n if (options.scope) params.set(\"scope\", options.scope);\n if (options.state) params.set(\"state\", options.state);\n if (options.labels?.length) params.set(\"labels\", options.labels.join(\",\"));\n if (options.search) params.set(\"search\", options.search);\n if (options.perPage) params.set(\"per_page\", options.perPage.toString());\n\n const base = options.projectId\n ? `/projects/${encodeProjectId(options.projectId)}/issues`\n : \"/issues\";\n\n return gitlabFetch<GitLabIssue[]>(`${base}${buildQuery(params)}`);\n}\n\nexport function getIssue(projectId: number | string, issueIid: number): Promise<GitLabIssue> {\n return gitlabFetch<GitLabIssue>(`/projects/${encodeProjectId(projectId)}/issues/${issueIid}`);\n}\n\nexport function createIssue(\n projectId: number | string,\n options: {\n title: string;\n description?: string;\n labels?: string[];\n assigneeIds?: number[];\n milestoneId?: number;\n dueDate?: string;\n },\n): Promise<GitLabIssue> {\n const body: Record<string, unknown> = { title: options.title };\n\n if (options.description) body.description = options.description;\n if (options.labels?.length) body.labels = options.labels.join(\",\");\n if (options.assigneeIds?.length) body.assignee_ids = options.assigneeIds;\n if (options.milestoneId) body.milestone_id = options.milestoneId;\n if (options.dueDate) body.due_date = options.dueDate;\n\n return gitlabFetch<GitLabIssue>(`/projects/${encodeProjectId(projectId)}/issues`, {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport function updateIssue(\n projectId: number | string,\n issueIid: number,\n options: {\n title?: string;\n description?: string;\n state?: \"opened\" | \"closed\";\n labels?: string[];\n assigneeIds?: number[];\n },\n): Promise<GitLabIssue> {\n const body: Record<string, unknown> = {};\n\n if (options.title) body.title = options.title;\n if (options.description !== undefined) body.description = options.description;\n if (options.state) body.state_event = options.state === \"closed\" ? \"close\" : \"reopen\";\n if (options.labels) body.labels = options.labels.join(\",\");\n if (options.assigneeIds) body.assignee_ids = options.assigneeIds;\n\n return gitlabFetch<GitLabIssue>(`/projects/${encodeProjectId(projectId)}/issues/${issueIid}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n });\n}\n\nexport function listMergeRequests(options?: {\n scope?: \"created_by_me\" | \"assigned_to_me\" | \"all\";\n state?: \"opened\" | \"closed\" | \"merged\" | \"all\";\n labels?: string[];\n projectId?: number | string;\n perPage?: number;\n}): Promise<GitLabMergeRequest[]> {\n const params = new URLSearchParams();\n\n if (options?.scope) params.set(\"scope\", options.scope);\n if (options?.state) params.set(\"state\", options.state);\n if (options?.labels?.length) params.set(\"labels\", options.labels.join(\",\"));\n if (options?.perPage) params.set(\"per_page\", options.perPage.toString());\n\n const base = options?.projectId\n ? `/projects/${encodeProjectId(options.projectId)}/merge_requests`\n : \"/merge_requests\";\n\n return gitlabFetch<GitLabMergeRequest[]>(`${base}${buildQuery(params)}`);\n}\n\nexport function getMergeRequest(\n projectId: number | string,\n mrIid: number,\n): Promise<GitLabMergeRequest> {\n return gitlabFetch<GitLabMergeRequest>(\n `/projects/${encodeProjectId(projectId)}/merge_requests/${mrIid}`,\n );\n}\n\nexport function formatIssueForDisplay(issue: GitLabIssue): string {\n const assignees = issue.assignees.map((a) => `@${a.username}`).join(\", \");\n const labels = issue.labels.length ? `[${issue.labels.join(\", \")}]` : \"\";\n\n return `#${issue.iid}: ${issue.title} ${labels}\nState: ${issue.state}\nAssignees: ${assignees || \"None\"}\nCreated: ${new Date(issue.created_at).toLocaleDateString()}\nURL: ${issue.web_url}`;\n}\n\nexport function formatMergeRequestForDisplay(mr: GitLabMergeRequest): string {\n const assignees = mr.assignees.map((a) => `@${a.username}`).join(\", \");\n const reviewers = mr.reviewers.map((r) => `@${r.username}`).join(\", \");\n const labels = mr.labels.length ? `[${mr.labels.join(\", \")}]` : \"\";\n\n return `!${mr.iid}: ${mr.title} ${labels}\nState: ${mr.state}${mr.draft ? \" (Draft)\" : \"\"}\nSource: ${mr.source_branch} → Target: ${mr.target_branch}\nAuthor: @${mr.author.username}\nAssignees: ${assignees || \"None\"}\nReviewers: ${reviewers || \"None\"}\nCreated: ${new Date(mr.created_at).toLocaleDateString()}\nURL: ${mr.web_url}`;\n}\n",
|
|
170
|
+
"tools/search-issues.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatIssueForDisplay, searchIssues } from \"../../lib/gitlab-client.ts\";\n\nexport default tool({\n id: \"search-issues\",\n description:\n \"Search for issues in GitLab projects. Can search across all accessible projects or within a specific project. Returns issue titles, states, assignees, and labels.\",\n inputSchema: z.object({\n scope: z\n .enum([\"created_by_me\", \"assigned_to_me\", \"all\"])\n .default(\"all\")\n .describe(\"Scope of issues to search\"),\n state: z\n .enum([\"opened\", \"closed\", \"all\"])\n .default(\"opened\")\n .describe(\"State of issues to search for\"),\n search: z.string().optional().describe(\"Search query to filter issues by title or description\"),\n labels: z.array(z.string()).optional().describe('Filter by labels (e.g., [\"bug\", \"urgent\"])'),\n projectId: z\n .union([z.number(), z.string()])\n .optional()\n .describe('Project ID or path (e.g., \"gitlab-org/gitlab\" or 278964)'),\n limit: z.number().min(1).max(100).default(20).describe(\"Maximum number of results to return\"),\n }),\n async execute({ scope, state, search, labels, projectId, limit }) {\n const issues = await searchIssues({\n scope,\n state,\n search,\n labels,\n projectId,\n perPage: limit,\n });\n\n if (!issues.length) {\n return {\n message: \"No issues found matching the criteria.\",\n count: 0,\n issues: [],\n };\n }\n\n return {\n count: issues.length,\n issues: issues.map((issue) => {\n const description = issue.description ?? \"\";\n const truncatedDescription =\n description.substring(0, 200) + (description.length > 200 ? \"...\" : \"\");\n\n return {\n id: issue.id,\n iid: issue.iid,\n projectId: issue.project_id,\n title: issue.title,\n state: issue.state,\n labels: issue.labels,\n assignees: issue.assignees.map((a) => ({\n username: a.username,\n name: a.name,\n })),\n author: {\n username: issue.author.username,\n name: issue.author.name,\n },\n createdAt: issue.created_at,\n updatedAt: issue.updated_at,\n webUrl: issue.web_url,\n description: truncatedDescription,\n };\n }),\n summary: issues.map(formatIssueForDisplay).join(\"\\n\\n\"),\n };\n },\n});\n",
|
|
171
|
+
"tools/create-issue.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createIssue } from \"../../lib/gitlab-client.ts\";\n\nexport default tool({\n id: \"create-issue\",\n description:\n \"Create a new issue in a GitLab project. Can set title, description, labels, assignees, milestone, and due date.\",\n inputSchema: z.object({\n projectId: z.union([z.number(), z.string()]).describe(\n 'Project ID or path (e.g., \"gitlab-org/gitlab\" or 278964)',\n ),\n title: z.string().min(1).describe(\"Issue title\"),\n description: z.string().optional().describe(\"Issue description in Markdown format\"),\n labels: z.array(z.string()).optional().describe('Labels to apply (e.g., [\"bug\", \"urgent\"])'),\n assigneeIds: z.array(z.number()).optional().describe(\"User IDs to assign the issue to\"),\n milestoneId: z.number().optional().describe(\"Milestone ID to associate with the issue\"),\n dueDate: z.string().optional().describe(\"Due date in YYYY-MM-DD format\"),\n }),\n async execute({ projectId, title, description, labels, assigneeIds, milestoneId, dueDate }) {\n const issue = await createIssue(projectId, {\n title,\n description,\n labels,\n assigneeIds,\n milestoneId,\n dueDate,\n });\n\n return {\n success: true,\n message: `Issue created successfully: #${issue.iid}`,\n issue: {\n id: issue.id,\n iid: issue.iid,\n projectId: issue.project_id,\n title: issue.title,\n state: issue.state,\n labels: issue.labels,\n assignees: issue.assignees.map(({ username, name }) => ({ username, name })),\n webUrl: issue.web_url,\n createdAt: issue.created_at,\n },\n };\n },\n});\n",
|
|
172
|
+
"tools/list-merge-requests.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatMergeRequestForDisplay, listMergeRequests } from \"../../lib/gitlab-client.ts\";\n\nexport default tool({\n id: \"list-merge-requests\",\n description:\n \"List merge requests in GitLab. Can filter by scope, state, labels, and specific project. Returns MR titles, states, branches, assignees, and reviewers.\",\n inputSchema: z.object({\n scope: z\n .enum([\"created_by_me\", \"assigned_to_me\", \"all\"])\n .default(\"all\")\n .describe(\"Scope of merge requests to list\"),\n state: z\n .enum([\"opened\", \"closed\", \"merged\", \"all\"])\n .default(\"opened\")\n .describe(\"State of merge requests to list\"),\n labels: z\n .array(z.string())\n .optional()\n .describe('Filter by labels (e.g., [\"feature\", \"review-needed\"])'),\n projectId: z\n .union([z.number(), z.string()])\n .optional()\n .describe('Project ID or path (e.g., \"gitlab-org/gitlab\" or 278964)'),\n limit: z.number().min(1).max(100).default(20).describe(\"Maximum number of results to return\"),\n }),\n async execute({ scope, state, labels, projectId, limit }) {\n const mergeRequests = await listMergeRequests({\n scope,\n state,\n labels,\n projectId,\n perPage: limit,\n });\n\n if (!mergeRequests.length) {\n return {\n message: \"No merge requests found matching the criteria.\",\n count: 0,\n mergeRequests: [],\n };\n }\n\n return {\n count: mergeRequests.length,\n mergeRequests: mergeRequests.map((mr) => {\n const description = mr.description ?? \"\";\n const truncatedDescription =\n description.substring(0, 200) + (description.length > 200 ? \"...\" : \"\");\n\n return {\n id: mr.id,\n iid: mr.iid,\n projectId: mr.project_id,\n title: mr.title,\n state: mr.state,\n draft: mr.draft,\n sourceBranch: mr.source_branch,\n targetBranch: mr.target_branch,\n labels: mr.labels,\n author: {\n username: mr.author.username,\n name: mr.author.name,\n },\n assignees: mr.assignees.map((a) => ({\n username: a.username,\n name: a.name,\n })),\n reviewers: mr.reviewers.map((r) => ({\n username: r.username,\n name: r.name,\n })),\n createdAt: mr.created_at,\n updatedAt: mr.updated_at,\n mergedAt: mr.merged_at,\n webUrl: mr.web_url,\n description: truncatedDescription,\n };\n }),\n summary: mergeRequests.map(formatMergeRequestForDisplay).join(\"\\n\\n\"),\n };\n },\n});\n",
|
|
173
|
+
"tools/list-projects.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listProjects } from \"../../lib/gitlab-client.ts\";\n\nexport default tool({\n id: \"list-projects\",\n description:\n \"List GitLab projects accessible to the authenticated user. Can search, filter by membership, and sort results.\",\n inputSchema: z.object({\n search: z.string().optional().describe(\"Search query to filter projects by name or path\"),\n membership: z.boolean().default(true).describe(\"Only show projects where user is a member\"),\n orderBy: z\n .enum([\"id\", \"name\", \"created_at\", \"updated_at\", \"last_activity_at\"])\n .default(\"last_activity_at\")\n .describe(\"Field to order results by\"),\n sort: z.enum([\"asc\", \"desc\"]).default(\"desc\").describe(\"Sort direction\"),\n limit: z.number().min(1).max(100).default(20).describe(\"Maximum number of results to return\"),\n }),\n async execute({ search, membership, orderBy, sort, limit }) {\n const projects = await listProjects({\n search,\n membership,\n orderBy,\n sort,\n perPage: limit,\n });\n\n const mappedProjects = projects.map((project) => ({\n id: project.id,\n name: project.name,\n nameWithNamespace: project.name_with_namespace,\n path: project.path_with_namespace,\n description: project.description ?? \"No description\",\n visibility: project.visibility,\n defaultBranch: project.default_branch,\n webUrl: project.web_url,\n createdAt: project.created_at,\n lastActivityAt: project.last_activity_at,\n }));\n\n if (mappedProjects.length === 0) {\n return {\n message: \"No projects found matching the criteria.\",\n count: 0,\n projects: [],\n };\n }\n\n return {\n count: mappedProjects.length,\n projects: mappedProjects,\n };\n },\n});\n",
|
|
174
|
+
"tools/get-issue.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getIssue } from \"../../lib/gitlab-client.ts\";\n\nexport default tool({\n id: \"get-issue\",\n description:\n \"Get detailed information about a specific GitLab issue including full description, comments, time tracking, and metadata.\",\n inputSchema: z.object({\n projectId: z\n .union([z.number(), z.string()])\n .describe('Project ID or path (e.g., \"gitlab-org/gitlab\" or 278964)'),\n issueIid: z\n .number()\n .describe(\n \"Issue IID (internal ID, the number shown in the issue URL like #123)\",\n ),\n }),\n async execute({ projectId, issueIid }) {\n const issue = await getIssue(projectId, issueIid);\n\n return {\n id: issue.id,\n iid: issue.iid,\n projectId: issue.project_id,\n title: issue.title,\n description: issue.description ?? \"No description provided\",\n state: issue.state,\n labels: issue.labels,\n milestone: issue.milestone\n ? { id: issue.milestone.id, title: issue.milestone.title }\n : null,\n assignees: issue.assignees.map((a) => ({\n id: a.id,\n username: a.username,\n name: a.name,\n avatarUrl: a.avatar_url,\n })),\n author: {\n id: issue.author.id,\n username: issue.author.username,\n name: issue.author.name,\n avatarUrl: issue.author.avatar_url,\n },\n timeStats: {\n timeEstimate: issue.time_stats.time_estimate,\n totalTimeSpent: issue.time_stats.total_time_spent,\n },\n createdAt: issue.created_at,\n updatedAt: issue.updated_at,\n closedAt: issue.closed_at,\n webUrl: issue.web_url,\n };\n },\n});\n",
|
|
175
|
+
"app/api/auth/gitlab/route.ts": "import { createOAuthInitHandler, gitlabConfig, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(gitlabConfig, { tokenStore: memoryTokenStore });\n",
|
|
176
|
+
"app/api/auth/gitlab/callback/route.ts": "import { createOAuthCallbackHandler, gitlabConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(gitlabConfig, { tokenStore: hybridTokenStore });\n",
|
|
177
|
+
".env.example": "# GitLab OAuth Configuration\n# Create a new application at: https://gitlab.com/-/profile/applications\n# Set the redirect URI to: http://localhost:3000/api/auth/gitlab/callback\n# (Update the URL for production)\n\nGITLAB_CLIENT_ID=your_gitlab_application_id\nGITLAB_CLIENT_SECRET=your_gitlab_application_secret\n"
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
"integration:discord": {
|
|
181
|
+
"files": {
|
|
182
|
+
"lib/discord-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst DISCORD_API_VERSION = \"v10\";\nconst DISCORD_BASE_URL = `https://discord.com/api/${DISCORD_API_VERSION}`;\n\ninterface DiscordUser {\n id: string;\n username: string;\n discriminator: string;\n global_name?: string | null;\n avatar?: string | null;\n bot?: boolean;\n system?: boolean;\n mfa_enabled?: boolean;\n banner?: string | null;\n accent_color?: number | null;\n locale?: string;\n verified?: boolean;\n email?: string | null;\n flags?: number;\n premium_type?: number;\n public_flags?: number;\n}\n\ninterface DiscordGuild {\n id: string;\n name: string;\n icon?: string | null;\n owner?: boolean;\n permissions?: string;\n features: string[];\n}\n\ninterface DiscordChannel {\n id: string;\n type: number;\n guild_id?: string;\n position?: number;\n name?: string;\n topic?: string | null;\n nsfw?: boolean;\n last_message_id?: string | null;\n bitrate?: number;\n user_limit?: number;\n rate_limit_per_user?: number;\n recipients?: DiscordUser[];\n icon?: string | null;\n owner_id?: string;\n application_id?: string;\n parent_id?: string | null;\n last_pin_timestamp?: string | null;\n rtc_region?: string | null;\n video_quality_mode?: number;\n message_count?: number;\n member_count?: number;\n flags?: number;\n}\n\ninterface DiscordMessage {\n id: string;\n channel_id: string;\n author: DiscordUser;\n content: string;\n timestamp: string;\n edited_timestamp?: string | null;\n tts: boolean;\n mention_everyone: boolean;\n mentions: DiscordUser[];\n mention_roles: string[];\n attachments: Array<{\n id: string;\n filename: string;\n size: number;\n url: string;\n proxy_url: string;\n height?: number | null;\n width?: number | null;\n content_type?: string;\n }>;\n embeds: unknown[];\n reactions?: Array<{\n count: number;\n me: boolean;\n emoji: {\n id: string | null;\n name: string | null;\n };\n }>;\n pinned: boolean;\n type: number;\n}\n\ninterface DiscordGuildMember {\n user?: DiscordUser;\n nick?: string | null;\n avatar?: string | null;\n roles: string[];\n joined_at: string;\n premium_since?: string | null;\n deaf: boolean;\n mute: boolean;\n flags: number;\n pending?: boolean;\n permissions?: string;\n communication_disabled_until?: string | null;\n}\n\nasync function discordFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Discord. Please connect your account.\");\n }\n\n const response = await fetch(`${DISCORD_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as { message?: string }));\n throw new Error(\n `Discord API error: ${response.status} ${error.message ?? response.statusText}`,\n );\n }\n\n return response.json();\n}\n\nfunction buildQuery(\n options: Record<string, string | number | undefined>,\n limits?: Record<string, number>,\n): string {\n const params = new URLSearchParams();\n\n for (const [key, value] of Object.entries(options)) {\n if (value === undefined) continue;\n\n if (typeof value === \"number\") {\n const limit = limits?.[key];\n params.set(key, Math.min(value, limit ?? value).toString());\n continue;\n }\n\n params.set(key, value);\n }\n\n const query = params.toString();\n return query ? `?${query}` : \"\";\n}\n\nexport function getCurrentUser(): Promise<DiscordUser> {\n return discordFetch(\"/users/@me\");\n}\n\nexport function listGuilds(): Promise<DiscordGuild[]> {\n return discordFetch(\"/users/@me/guilds\");\n}\n\nexport function getGuild(guildId: string): Promise<DiscordGuild> {\n return discordFetch(`/guilds/${guildId}`);\n}\n\nexport function listChannels(guildId: string): Promise<DiscordChannel[]> {\n return discordFetch(`/guilds/${guildId}/channels`);\n}\n\nexport function getChannel(channelId: string): Promise<DiscordChannel> {\n return discordFetch(`/channels/${channelId}`);\n}\n\nexport function getMessages(\n channelId: string,\n options?: {\n limit?: number;\n before?: string;\n after?: string;\n around?: string;\n },\n): Promise<DiscordMessage[]> {\n const query = buildQuery(\n {\n limit: options?.limit,\n before: options?.before,\n after: options?.after,\n around: options?.around,\n },\n { limit: 100 },\n );\n\n return discordFetch(`/channels/${channelId}/messages${query}`);\n}\n\nexport function sendMessage(\n channelId: string,\n content: string,\n options?: {\n tts?: boolean;\n embeds?: unknown[];\n },\n): Promise<DiscordMessage> {\n const body: Record<string, unknown> = { content };\n\n if (options?.tts !== undefined) body.tts = options.tts;\n if (options?.embeds) body.embeds = options.embeds;\n\n return discordFetch(`/channels/${channelId}/messages`, {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport function getGuildMembers(\n guildId: string,\n options?: {\n limit?: number;\n after?: string;\n },\n): Promise<DiscordGuildMember[]> {\n const query = buildQuery(\n { limit: options?.limit, after: options?.after },\n { limit: 1000 },\n );\n\n return discordFetch(`/guilds/${guildId}/members${query}`);\n}\n\nexport function formatUsername(user: DiscordUser): string {\n if (user.discriminator === \"0\") return user.username;\n return `${user.username}#${user.discriminator}`;\n}\n\nfunction getCdnAssetUrl(\n basePath: string,\n id: string,\n hash: string | null | undefined,\n size: number,\n): string | null {\n if (!hash) return null;\n const extension = hash.startsWith(\"a_\") ? \"gif\" : \"png\";\n return `https://cdn.discordapp.com/${basePath}/${id}/${hash}.${extension}?size=${size}`;\n}\n\nexport function getAvatarUrl(user: DiscordUser, size: number = 128): string | null {\n return getCdnAssetUrl(\"avatars\", user.id, user.avatar, size);\n}\n\nexport function getGuildIconUrl(guild: DiscordGuild, size: number = 128): string | null {\n return getCdnAssetUrl(\"icons\", guild.id, guild.icon, size);\n}\n\nexport function getChannelTypeName(type: number): string {\n const types: Record<number, string> = {\n 0: \"Text\",\n 1: \"DM\",\n 2: \"Voice\",\n 3: \"Group DM\",\n 4: \"Category\",\n 5: \"Announcement\",\n 10: \"Announcement Thread\",\n 11: \"Public Thread\",\n 12: \"Private Thread\",\n 13: \"Stage Voice\",\n 14: \"Directory\",\n 15: \"Forum\",\n };\n\n return types[type] ?? \"Unknown\";\n}\n",
|
|
183
|
+
"tools/get-user.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatUsername, getAvatarUrl, getCurrentUser } from \"../../lib/discord-client.ts\";\n\nexport default tool({\n id: \"get-user\",\n description:\n \"Get information about the authenticated Discord user. Returns username, ID, avatar, and account details.\",\n inputSchema: z.object({\n includeAvatar: z.boolean().default(true).describe(\"Whether to include the avatar URL\"),\n }),\n async execute({ includeAvatar }) {\n const user = await getCurrentUser();\n\n const avatar = includeAvatar ? getAvatarUrl(user) : undefined;\n\n return {\n id: user.id,\n username: formatUsername(user),\n globalName: user.global_name,\n avatar,\n bot: user.bot,\n system: user.system,\n mfaEnabled: user.mfa_enabled,\n banner: user.banner,\n accentColor: user.accent_color,\n locale: user.locale,\n verified: user.verified,\n email: user.email,\n premiumType: user.premium_type,\n publicFlags: user.public_flags,\n };\n },\n});\n",
|
|
184
|
+
"tools/list-guilds.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getGuildIconUrl, listGuilds } from \"../../lib/discord-client.ts\";\n\nexport default tool({\n id: \"list-guilds\",\n description:\n \"List all Discord servers (guilds) the authenticated user is a member of. Returns server names, IDs, and basic information.\",\n inputSchema: z.object({\n includeIcons: z\n .boolean()\n .default(false)\n .describe(\"Whether to include icon URLs for servers\"),\n }),\n async execute({ includeIcons }) {\n const guilds = await listGuilds();\n\n return guilds.map((guild) => {\n const icon = includeIcons ? getGuildIconUrl(guild) : undefined;\n\n return {\n id: guild.id,\n name: guild.name,\n owner: guild.owner,\n icon,\n features: guild.features,\n permissions: guild.permissions,\n };\n });\n },\n});\n",
|
|
185
|
+
"tools/send-message.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatUsername, sendMessage } from \"../../lib/discord-client.ts\";\n\nexport default tool({\n id: \"send-message\",\n description: \"Send a message to a Discord channel. Returns the sent message details.\",\n inputSchema: z.object({\n channelId: z.string().describe(\"The ID of the Discord channel to send the message to\"),\n content: z\n .string()\n .min(1)\n .max(2000)\n .describe(\"The message content to send (1-2000 characters)\"),\n tts: z\n .boolean()\n .default(false)\n .describe(\"Whether this message should be sent as text-to-speech\"),\n }),\n async execute({ channelId, content, tts }) {\n const message = await sendMessage(channelId, content, { tts });\n\n return {\n id: message.id,\n content: message.content,\n channelId: message.channel_id,\n timestamp: message.timestamp,\n author: {\n id: message.author.id,\n username: formatUsername(message.author),\n globalName: message.author.global_name,\n },\n tts: message.tts,\n };\n },\n});\n",
|
|
186
|
+
"tools/get-messages.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatUsername, getMessages } from \"../../lib/discord-client.ts\";\n\nexport default tool({\n id: \"get-messages\",\n description:\n \"Get recent messages from a Discord channel. Returns message content, authors, timestamps, and attachments.\",\n inputSchema: z.object({\n channelId: z.string().describe(\"The ID of the Discord channel to get messages from\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(50)\n .describe(\"Maximum number of messages to retrieve (1-100)\"),\n before: z.string().optional().describe(\"Get messages before this message ID\"),\n after: z.string().optional().describe(\"Get messages after this message ID\"),\n }),\n async execute({ channelId, limit, before, after }) {\n const messages = await getMessages(channelId, { limit, before, after });\n\n return messages.map((message) => ({\n id: message.id,\n content: message.content,\n author: {\n id: message.author.id,\n username: formatUsername(message.author),\n globalName: message.author.global_name,\n bot: message.author.bot,\n },\n timestamp: message.timestamp,\n editedTimestamp: message.edited_timestamp,\n pinned: message.pinned,\n mentions: message.mentions.map((user) => ({\n id: user.id,\n username: formatUsername(user),\n })),\n attachments: message.attachments.map((attachment) => ({\n id: attachment.id,\n filename: attachment.filename,\n url: attachment.url,\n size: attachment.size,\n contentType: attachment.content_type,\n })),\n hasEmbeds: message.embeds.length > 0,\n reactions: message.reactions?.map((reaction) => ({\n emoji: reaction.emoji.name,\n count: reaction.count,\n meReacted: reaction.me,\n })),\n }));\n },\n});\n",
|
|
187
|
+
"tools/list-channels.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getChannelTypeName, listChannels } from \"../../lib/discord-client.ts\";\n\nexport default tool({\n id: \"list-channels\",\n description:\n \"List all channels in a Discord server (guild). Returns channel names, IDs, types, and basic information.\",\n inputSchema: z.object({\n guildId: z.string().describe(\"The ID of the Discord server (guild) to list channels from\"),\n includeCategories: z.boolean().default(true).describe(\"Whether to include category channels\"),\n }),\n async execute({ guildId, includeCategories }) {\n const channels = await listChannels(guildId);\n\n if (!includeCategories) {\n return channels\n .filter((channel) => channel.type !== 4)\n .map((channel) => ({\n id: channel.id,\n name: channel.name,\n type: getChannelTypeName(channel.type),\n typeId: channel.type,\n topic: channel.topic,\n nsfw: channel.nsfw,\n position: channel.position,\n parentId: channel.parent_id,\n lastMessageId: channel.last_message_id,\n }));\n }\n\n return channels.map((channel) => ({\n id: channel.id,\n name: channel.name,\n type: getChannelTypeName(channel.type),\n typeId: channel.type,\n topic: channel.topic,\n nsfw: channel.nsfw,\n position: channel.position,\n parentId: channel.parent_id,\n lastMessageId: channel.last_message_id,\n }));\n },\n});\n",
|
|
188
|
+
"app/api/auth/discord/route.ts": "import { createOAuthInitHandler, discordConfig, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(discordConfig, { tokenStore: memoryTokenStore });\n",
|
|
189
|
+
"app/api/auth/discord/callback/route.ts": "/**\n * Discord OAuth Callback\n *\n * Handles the OAuth callback from Discord and stores the tokens.\n */\n\nimport { createOAuthCallbackHandler, discordConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\n// Hybrid adapter: uses framework's memoryTokenStore for state (PKCE),\n// but user's tokenStore for actual token storage\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(discordConfig, { tokenStore: hybridTokenStore });\n",
|
|
190
|
+
".env.example": "# Discord OAuth Configuration\n# Get these from https://discord.com/developers/applications\n\n# Required: Your Discord application's Client ID\nDISCORD_CLIENT_ID=your_client_id_here\n\n# Required: Your Discord application's Client Secret\nDISCORD_CLIENT_SECRET=your_client_secret_here\n\n# Optional: Bot token for advanced bot features\n# Only needed if you want to use bot-specific functionality\nDISCORD_BOT_TOKEN=your_bot_token_here\n"
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
"integration:intercom": {
|
|
194
|
+
"files": {
|
|
195
|
+
"lib/intercom-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst INTERCOM_BASE_URL = \"https://api.intercom.io\";\n\ninterface IntercomResponse<T> {\n type: string;\n data?: T;\n pages?: {\n next?: string | null;\n page: number;\n per_page: number;\n total_pages: number;\n };\n}\n\ninterface IntercomContact {\n type: \"contact\";\n id: string;\n external_id?: string;\n email?: string;\n phone?: string;\n name?: string;\n avatar?: string;\n role?: string;\n created_at: number;\n updated_at: number;\n signed_up_at?: number;\n last_seen_at?: number;\n owner_id?: number;\n custom_attributes?: Record<string, unknown>;\n tags?: Array<{ id: string; name: string }>;\n}\n\ninterface IntercomConversation {\n type: \"conversation\";\n id: string;\n created_at: number;\n updated_at: number;\n source: {\n type: string;\n id: string;\n delivered_as: string;\n subject?: string;\n body?: string;\n author: {\n type: string;\n id: string;\n name?: string;\n email?: string;\n };\n };\n contacts?: Array<{\n type: string;\n id: string;\n }>;\n teammates?: Array<{\n type: string;\n id: string;\n }>;\n title?: string;\n state: \"open\" | \"closed\" | \"snoozed\";\n read: boolean;\n waiting_since?: number;\n snoozed_until?: number;\n priority?: \"priority\" | \"not_priority\";\n conversation_parts?: {\n type: string;\n conversation_parts: Array<{\n type: string;\n id: string;\n part_type: string;\n body: string;\n created_at: number;\n updated_at: number;\n author: {\n type: string;\n id: string;\n name?: string;\n email?: string;\n };\n }>;\n };\n}\n\ninterface IntercomMessageRequest {\n message_type: \"inapp\" | \"email\" | \"comment\";\n body: string;\n from: {\n type: \"admin\" | \"user\" | \"contact\";\n id: string;\n };\n to?: {\n type: \"user\" | \"contact\";\n id?: string;\n email?: string;\n };\n}\n\nfunction hasMorePages(pages?: IntercomResponse<unknown>[\"pages\"]): boolean {\n return pages ? pages.page < pages.total_pages : false;\n}\n\nasync function intercomFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Intercom. Please connect your account.\");\n }\n\n const response = await fetch(`${INTERCOM_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"Intercom-Version\": \"2.11\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as Record<string, unknown>));\n const message =\n (error as { errors?: Array<{ message?: string }>; message?: string }).errors?.[0]?.message ??\n (error as { message?: string }).message ??\n response.statusText;\n\n throw new Error(`Intercom API error: ${response.status} ${message}`);\n }\n\n return response.json() as Promise<T>;\n}\n\nexport async function listContacts(\n options: { page?: number; perPage?: number } = {},\n): Promise<{ contacts: IntercomContact[]; hasMore: boolean }> {\n const params = new URLSearchParams({\n per_page: String(options.perPage ?? 50),\n });\n\n if (options.page) params.set(\"page\", String(options.page));\n\n const response = await intercomFetch<IntercomResponse<IntercomContact[]>>(`/contacts?${params}`);\n\n return {\n contacts: response.data ?? [],\n hasMore: hasMorePages(response.pages),\n };\n}\n\nexport async function getContact(contactId: string): Promise<IntercomContact> {\n return intercomFetch<IntercomContact>(`/contacts/${contactId}`);\n}\n\nexport async function searchContacts(query: { email?: string; name?: string }): Promise<IntercomContact[]> {\n const value: Array<Record<string, unknown>> = [];\n\n if (query.email) {\n value.push({ field: \"email\", operator: \"=\", value: query.email });\n }\n\n if (query.name) {\n value.push({ field: \"name\", operator: \"~\", value: query.name });\n }\n\n const searchQuery = {\n query: {\n operator: \"AND\",\n value,\n },\n };\n\n const response = await intercomFetch<IntercomResponse<IntercomContact[]>>(\"/contacts/search\", {\n method: \"POST\",\n body: JSON.stringify(searchQuery),\n });\n\n return response.data ?? [];\n}\n\nexport async function listConversations(\n options: { page?: number; perPage?: number; open?: boolean } = {},\n): Promise<{ conversations: IntercomConversation[]; hasMore: boolean }> {\n const params = new URLSearchParams({\n per_page: String(options.perPage ?? 50),\n display_as: \"plaintext\",\n });\n\n if (options.page) params.set(\"page\", String(options.page));\n if (options.open !== undefined) params.set(\"state\", options.open ? \"open\" : \"closed\");\n\n const response = await intercomFetch<IntercomResponse<IntercomConversation[]>>(\n `/conversations?${params}`,\n );\n\n return {\n conversations: response.data ?? [],\n hasMore: hasMorePages(response.pages),\n };\n}\n\nexport async function getConversation(conversationId: string): Promise<IntercomConversation> {\n return intercomFetch<IntercomConversation>(`/conversations/${conversationId}`);\n}\n\nexport async function sendMessage(options: {\n conversationId?: string;\n body: string;\n messageType?: \"comment\" | \"note\";\n adminId?: string;\n}): Promise<IntercomConversation> {\n if (!options.conversationId) {\n throw new Error(\"conversationId is required to send a message\");\n }\n\n const body: Record<string, unknown> = {\n message_type: options.messageType ?? \"comment\",\n type: \"admin\",\n body: options.body,\n };\n\n if (options.adminId) body.admin_id = options.adminId;\n\n return intercomFetch<IntercomConversation>(`/conversations/${options.conversationId}/reply`, {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport async function createMessage(options: {\n contactId?: string;\n email?: string;\n body: string;\n messageType: \"inapp\" | \"email\";\n fromId: string;\n}): Promise<{ type: string; id: string }> {\n const messageBody: IntercomMessageRequest = {\n message_type: options.messageType,\n body: options.body,\n from: {\n type: \"admin\",\n id: options.fromId,\n },\n };\n\n if (options.contactId) {\n messageBody.to = { type: \"contact\", id: options.contactId };\n } else if (options.email) {\n messageBody.to = { type: \"contact\", email: options.email };\n } else {\n throw new Error(\"Either contactId or email is required\");\n }\n\n return intercomFetch<{ type: string; id: string }>(\"/messages\", {\n method: \"POST\",\n body: JSON.stringify(messageBody),\n });\n}\n\nexport async function getMe(): Promise<{ type: string; id: string; name: string; email: string }> {\n return intercomFetch<{ type: string; id: string; name: string; email: string }>(\"/me\");\n}\n",
|
|
196
|
+
"tools/get-conversation.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getConversation } from \"../../lib/intercom-client.ts\";\n\nfunction toIsoFromSeconds(seconds: number): string {\n return new Date(seconds * 1000).toISOString();\n}\n\nfunction toIsoFromSecondsOrNull(seconds?: number | null): string | null {\n return seconds ? toIsoFromSeconds(seconds) : null;\n}\n\nexport default tool({\n id: \"get-conversation\",\n description:\n \"Get details of a specific conversation from Intercom, including all conversation parts/messages.\",\n inputSchema: z.object({\n conversationId: z.string().describe(\"The ID of the conversation to retrieve\"),\n }),\n async execute({ conversationId }) {\n const conversation = await getConversation(conversationId);\n\n return {\n id: conversation.id,\n title: conversation.title,\n state: conversation.state,\n read: conversation.read,\n priority: conversation.priority,\n createdAt: toIsoFromSeconds(conversation.created_at),\n updatedAt: toIsoFromSeconds(conversation.updated_at),\n waitingSince: toIsoFromSecondsOrNull(conversation.waiting_since),\n snoozedUntil: toIsoFromSecondsOrNull(conversation.snoozed_until),\n source: {\n type: conversation.source.type,\n subject: conversation.source.subject,\n body: conversation.source.body,\n author: {\n type: conversation.source.author.type,\n id: conversation.source.author.id,\n name: conversation.source.author.name,\n email: conversation.source.author.email,\n },\n },\n conversationParts:\n conversation.conversation_parts?.conversation_parts.map((part) => ({\n id: part.id,\n partType: part.part_type,\n body: part.body,\n createdAt: toIsoFromSeconds(part.created_at),\n author: {\n type: part.author.type,\n id: part.author.id,\n name: part.author.name,\n email: part.author.email,\n },\n })) ?? [],\n contactIds: conversation.contacts?.map((c) => c.id),\n teammateIds: conversation.teammates?.map((t) => t.id),\n };\n },\n});\n",
|
|
197
|
+
"tools/send-message.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { sendMessage } from \"../../lib/intercom-client.ts\";\n\nexport default tool({\n id: \"send-message\",\n description: \"Send a message or reply to an existing conversation in Intercom.\",\n inputSchema: z.object({\n conversationId: z.string().describe(\"The ID of the conversation to reply to\"),\n body: z.string().describe(\"The message content to send\"),\n messageType: z\n .enum([\"comment\", \"note\"])\n .default(\"comment\")\n .describe(\"Type of message: 'comment' (visible to user) or 'note' (internal only)\"),\n adminId: z.string().optional().describe(\"The ID of the admin sending the message\"),\n }),\n async execute({ conversationId, body, messageType, adminId }) {\n const conversation = await sendMessage({ conversationId, body, messageType, adminId });\n\n return {\n success: true,\n conversation: {\n id: conversation.id,\n state: conversation.state,\n updatedAt: new Date(conversation.updated_at * 1000).toISOString(),\n },\n message: `Message sent successfully to conversation ${conversationId}`,\n };\n },\n});\n",
|
|
198
|
+
"tools/list-contacts.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listContacts } from \"../../lib/intercom-client.ts\";\n\nexport default tool({\n id: \"list-contacts\",\n description:\n \"List contacts from Intercom workspace. Returns contact information including email, name, and metadata.\",\n inputSchema: z.object({\n page: z.number().min(1).default(1).describe(\"Page number for pagination\"),\n perPage: z\n .number()\n .min(1)\n .max(150)\n .default(50)\n .describe(\"Number of contacts per page (max 150)\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of contacts to return\"),\n }),\n async execute({ page, perPage, limit }) {\n const { contacts, hasMore } = await listContacts({ page, perPage });\n\n return {\n contacts: contacts.slice(0, limit).map((contact) => ({\n id: contact.id,\n email: contact.email,\n name: contact.name,\n phone: contact.phone,\n role: contact.role,\n createdAt: new Date(contact.created_at * 1000).toISOString(),\n updatedAt: new Date(contact.updated_at * 1000).toISOString(),\n lastSeenAt: contact.last_seen_at\n ? new Date(contact.last_seen_at * 1000).toISOString()\n : null,\n ownerId: contact.owner_id,\n tags: contact.tags?.map((tag) => tag.name),\n })),\n hasMore,\n page,\n };\n },\n});\n",
|
|
199
|
+
"tools/get-contact.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getContact } from \"../../lib/intercom-client.ts\";\n\nfunction toIsoOrNull(timestamp?: number | null): string | null {\n return timestamp ? new Date(timestamp * 1000).toISOString() : null;\n}\n\nexport default tool({\n id: \"get-contact\",\n description: \"Get details of a specific contact from Intercom by their ID.\",\n inputSchema: z.object({\n contactId: z.string().describe(\"The ID of the contact to retrieve\"),\n }),\n async execute({ contactId }) {\n const contact = await getContact(contactId);\n\n return {\n id: contact.id,\n email: contact.email,\n name: contact.name,\n phone: contact.phone,\n role: contact.role,\n externalId: contact.external_id,\n avatar: contact.avatar,\n createdAt: new Date(contact.created_at * 1000).toISOString(),\n updatedAt: new Date(contact.updated_at * 1000).toISOString(),\n signedUpAt: toIsoOrNull(contact.signed_up_at),\n lastSeenAt: toIsoOrNull(contact.last_seen_at),\n ownerId: contact.owner_id,\n customAttributes: contact.custom_attributes,\n tags: contact.tags?.map((tag) => ({ id: tag.id, name: tag.name })),\n };\n },\n});\n",
|
|
200
|
+
"tools/list-conversations.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listConversations } from \"../../lib/intercom-client.ts\";\n\nfunction toIsoSeconds(seconds?: number | null): string | null {\n if (!seconds) return null;\n return new Date(seconds * 1000).toISOString();\n}\n\nexport default tool({\n id: \"list-conversations\",\n description: \"List conversations from Intercom. Can filter by open/closed status.\",\n inputSchema: z.object({\n page: z.number().min(1).default(1).describe(\"Page number for pagination\"),\n perPage: z\n .number()\n .min(1)\n .max(150)\n .default(50)\n .describe(\"Number of conversations per page (max 150)\"),\n open: z\n .boolean()\n .optional()\n .describe(\"Filter by open (true) or closed (false) conversations\"),\n limit: z\n .number()\n .min(1)\n .max(50)\n .default(20)\n .describe(\"Maximum number of conversations to return\"),\n }),\n async execute({ page, perPage, open, limit }) {\n const { conversations, hasMore } = await listConversations({ page, perPage, open });\n\n return {\n conversations: conversations.slice(0, limit).map((conv) => ({\n id: conv.id,\n title: conv.title,\n state: conv.state,\n read: conv.read,\n priority: conv.priority,\n createdAt: toIsoSeconds(conv.created_at) as string,\n updatedAt: toIsoSeconds(conv.updated_at) as string,\n waitingSince: toIsoSeconds(conv.waiting_since),\n snoozedUntil: toIsoSeconds(conv.snoozed_until),\n source: {\n type: conv.source.type,\n subject: conv.source.subject,\n body: conv.source.body,\n author: {\n id: conv.source.author.id,\n name: conv.source.author.name,\n email: conv.source.author.email,\n },\n },\n contactIds: conv.contacts?.map((c) => c.id),\n teammateIds: conv.teammates?.map((t) => t.id),\n })),\n hasMore,\n page,\n };\n },\n});\n",
|
|
201
|
+
"app/api/auth/intercom/route.ts": "import { createOAuthInitHandler, intercomConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(intercomConfig);\n",
|
|
202
|
+
"app/api/auth/intercom/callback/route.ts": "import { createOAuthCallbackHandler, intercomConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(intercomConfig, { tokenStore: hybridTokenStore });\n",
|
|
203
|
+
".env.example": "# Intercom OAuth Configuration\n# Get your credentials from https://developers.intercom.com/building-apps/\nINTERCOM_CLIENT_ID=your-client-id\nINTERCOM_CLIENT_SECRET=your-client-secret\n"
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"integration:anthropic": {
|
|
207
|
+
"files": {
|
|
208
|
+
"lib/anthropic-admin-client.ts": "/**\n * Anthropic Admin API Client\n *\n * Provides methods to interact with the Anthropic Admin API for organization management.\n * Requires an admin API key with appropriate permissions.\n *\n * @see https://docs.anthropic.com/en/api/admin-api\n */\n\nconst ANTHROPIC_ADMIN_API_BASE_URL = 'https://api.anthropic.com/v1/admin';\n\nexport interface AnthropicWorkspace {\n id: string;\n name: string;\n display_name: string;\n created_at: string;\n}\n\nexport interface AnthropicUsageRecord {\n workspace_id: string;\n date: string;\n model: string;\n input_tokens: number;\n output_tokens: number;\n cache_creation_tokens?: number;\n cache_read_tokens?: number;\n total_cost_usd: number;\n}\n\nexport interface AnthropicAPIKey {\n id: string;\n name: string;\n workspace_id?: string;\n created_at: string;\n last_used_at?: string;\n status: 'active' | 'revoked';\n key_type: 'admin' | 'workspace' | 'service';\n}\n\nexport interface AnthropicMember {\n id: string;\n email: string;\n role: 'owner' | 'admin' | 'member' | 'developer';\n status: 'active' | 'pending' | 'inactive';\n created_at: string;\n last_active_at?: string;\n}\n\nexport interface AnthropicOrganization {\n id: string;\n name: string;\n display_name: string;\n created_at: string;\n settings: {\n default_model?: string;\n rate_limit_tier?: string;\n billing_email?: string;\n };\n}\n\nexport interface AnthropicUsageOptions {\n startDate: string;\n endDate: string;\n workspaceId?: string;\n model?: string;\n granularity?: 'day' | 'hour';\n}\n\nexport class AnthropicAdminError extends Error {\n constructor(\n message: string,\n public statusCode?: number,\n public response?: unknown\n ) {\n super(message);\n this.name = 'AnthropicAdminError';\n }\n}\n\n/**\n * Client for interacting with the Anthropic Admin API\n */\nexport class AnthropicAdminClient {\n private apiKey: string;\n private baseUrl: string;\n\n constructor(apiKey?: string, baseUrl?: string) {\n this.apiKey = apiKey ?? process.env.ANTHROPIC_ADMIN_API_KEY ?? '';\n this.baseUrl = baseUrl ?? ANTHROPIC_ADMIN_API_BASE_URL;\n\n if (!this.apiKey) {\n throw new AnthropicAdminError(\n 'ANTHROPIC_ADMIN_API_KEY is required. Please set it in your environment variables.'\n );\n }\n\n if (!this.apiKey.startsWith('sk-ant-')) {\n throw new AnthropicAdminError(\n 'Invalid Anthropic API key format. Admin keys should start with \"sk-ant-\"'\n );\n }\n }\n\n private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const response = await fetch(`${this.baseUrl}${endpoint}`, {\n ...options,\n headers: {\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01',\n 'Content-Type': 'application/json',\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new AnthropicAdminError(\n errorData?.error?.message ?? `API request failed: ${response.statusText}`,\n response.status,\n errorData\n );\n }\n\n return response.json();\n }\n\n /**\n * List all workspaces in the organization\n */\n async listWorkspaces(): Promise<{ workspaces: AnthropicWorkspace[] }> {\n return this.request('/workspaces');\n }\n\n /**\n * Get details for a specific workspace\n */\n async getWorkspace(workspaceId: string): Promise<AnthropicWorkspace> {\n if (!workspaceId) throw new AnthropicAdminError('workspaceId is required');\n return this.request(`/workspaces/${workspaceId}`);\n }\n\n /**\n * Get API usage statistics for a date range\n *\n * @param options - Usage query options\n * @returns Usage records grouped by date, workspace, and model\n */\n async getUsage(options: AnthropicUsageOptions): Promise<{\n usage: AnthropicUsageRecord[];\n total_cost_usd: number;\n }> {\n const { startDate, endDate, workspaceId, model, granularity = 'day' } = options;\n\n if (!startDate || !endDate) {\n throw new AnthropicAdminError('startDate and endDate are required');\n }\n\n // Validate date format (YYYY-MM-DD)\n const dateRegex = /^\\d{4}-\\d{2}-\\d{2}$/;\n if (!dateRegex.test(startDate) || !dateRegex.test(endDate)) {\n throw new AnthropicAdminError('Dates must be in YYYY-MM-DD format');\n }\n\n const params = new URLSearchParams({\n start_date: startDate,\n end_date: endDate,\n granularity,\n });\n\n if (workspaceId) params.append('workspace_id', workspaceId);\n if (model) params.append('model', model);\n\n return this.request(`/usage?${params.toString()}`);\n }\n\n /**\n * List API keys for the organization or a specific workspace\n */\n async listAPIKeys(workspaceId?: string): Promise<{ api_keys: AnthropicAPIKey[] }> {\n const endpoint = workspaceId ? `/workspaces/${workspaceId}/api-keys` : '/api-keys';\n return this.request(endpoint);\n }\n\n /**\n * List all members in the organization\n */\n async listMembers(): Promise<{ members: AnthropicMember[] }> {\n return this.request('/members');\n }\n\n /**\n * Get organization details and settings\n */\n async getOrganization(): Promise<AnthropicOrganization> {\n return this.request('/organization');\n }\n\n /**\n * Create a new API key (admin functionality)\n */\n async createAPIKey(data: {\n name: string;\n workspace_id?: string;\n key_type?: 'workspace' | 'service';\n }): Promise<{ api_key: AnthropicAPIKey & { key: string } }> {\n return this.request('/api-keys', {\n method: 'POST',\n body: JSON.stringify(data),\n });\n }\n\n /**\n * Revoke an API key\n */\n async revokeAPIKey(keyId: string): Promise<{ success: boolean }> {\n if (!keyId) throw new AnthropicAdminError('keyId is required');\n\n return this.request(`/api-keys/${keyId}/revoke`, {\n method: 'POST',\n });\n }\n}\n\n/**\n * Get a singleton instance of the Anthropic Admin client\n */\nlet client: AnthropicAdminClient | null = null;\n\nexport function getAnthropicAdminClient(): AnthropicAdminClient {\n client ??= new AnthropicAdminClient();\n return client;\n}\n\nexport default AnthropicAdminClient;\n",
|
|
209
|
+
"tools/get-usage.ts": "import { tool } from 'veryfront/tool';\nimport { z } from 'zod';\nimport { getAnthropicAdminClient } from '../../lib/anthropic-admin-client';\n\nexport const getUsage = tool({\n name: 'get_usage',\n description:\n 'Get API usage statistics for a specific date range. Returns token usage and costs broken down by date, workspace, and model. Dates must be in YYYY-MM-DD format.',\n parameters: z.object({\n startDate: z\n .string()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'Date must be in YYYY-MM-DD format')\n .describe('Start date for usage query (YYYY-MM-DD format, e.g., 2025-01-01)'),\n endDate: z\n .string()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'Date must be in YYYY-MM-DD format')\n .describe('End date for usage query (YYYY-MM-DD format, e.g., 2025-01-31)'),\n workspaceId: z\n .string()\n .optional()\n .describe('Optional workspace ID to filter usage by specific workspace'),\n model: z\n .string()\n .optional()\n .describe(\n 'Optional model name to filter usage (e.g., claude-3-opus-20240229, claude-3-sonnet-20240229)'\n ),\n granularity: z\n .enum(['day', 'hour'])\n .default('day')\n .describe('Time granularity for usage aggregation (day or hour)'),\n }),\n execute: async ({ startDate, endDate, workspaceId, model, granularity }) => {\n try {\n const client = getAnthropicAdminClient();\n const result = await client.getUsage({\n startDate,\n endDate,\n workspaceId,\n model,\n granularity,\n });\n\n const totals = result.usage.reduce(\n (acc, record) => {\n acc.input += record.input_tokens;\n acc.output += record.output_tokens;\n acc.cacheCreation += record.cache_creation_tokens ?? 0;\n acc.cacheRead += record.cache_read_tokens ?? 0;\n return acc;\n },\n { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 }\n );\n\n return {\n success: true,\n usage: result.usage,\n summary: {\n total_cost_usd: result.total_cost_usd,\n total_input_tokens: totals.input,\n total_output_tokens: totals.output,\n total_cache_creation_tokens: totals.cacheCreation,\n total_cache_read_tokens: totals.cacheRead,\n record_count: result.usage.length,\n date_range: {\n start: startDate,\n end: endDate,\n },\n filters: {\n workspace_id: workspaceId,\n model,\n granularity,\n },\n },\n message: `Retrieved ${result.usage.length} usage record(s) totaling $${result.total_cost_usd.toFixed(4)} USD`,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to retrieve usage data',\n usage: [],\n };\n }\n },\n});\n\nexport default getUsage;\n",
|
|
210
|
+
"tools/list-workspaces.ts": "import { tool } from 'veryfront/tool';\nimport { z } from 'zod';\nimport { getAnthropicAdminClient } from '../../lib/anthropic-admin-client';\n\nexport const listWorkspaces = tool({\n name: 'list_workspaces',\n description:\n 'List all workspaces in the Anthropic organization. Workspaces allow you to organize API keys, usage, and permissions for different teams or projects.',\n parameters: z.object({}),\n execute: async () => {\n try {\n const client = getAnthropicAdminClient();\n const { workspaces } = await client.listWorkspaces();\n const count = workspaces.length;\n\n return {\n success: true,\n workspaces,\n count,\n message: `Found ${count} workspace(s)`,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to list workspaces',\n workspaces: [],\n };\n }\n },\n});\n\nexport default listWorkspaces;\n",
|
|
211
|
+
"tools/list-api-keys.ts": "import { tool } from 'veryfront/tool';\nimport { z } from 'zod';\nimport { getAnthropicAdminClient } from '../../lib/anthropic-admin-client';\n\nexport const listAPIKeys = tool({\n name: 'list_api_keys',\n description:\n 'List all API keys for the organization or a specific workspace. Returns key metadata including name, status, type, and usage information. The actual key values are not returned for security reasons.',\n parameters: z.object({\n workspaceId: z\n .string()\n .optional()\n .describe(\n 'Optional workspace ID to filter API keys by workspace. If not provided, lists all organization API keys'\n ),\n }),\n execute: async ({ workspaceId }) => {\n try {\n const client = getAnthropicAdminClient();\n const { api_keys } = await client.listAPIKeys(workspaceId);\n\n const active = api_keys.filter(key => key.status === 'active').length;\n const revoked = api_keys.filter(key => key.status === 'revoked').length;\n\n const by_type = api_keys.reduce<Record<string, number>>((acc, key) => {\n acc[key.key_type] = (acc[key.key_type] ?? 0) + 1;\n return acc;\n }, {});\n\n return {\n success: true,\n api_keys,\n summary: {\n total: api_keys.length,\n active,\n revoked,\n by_type,\n workspace_id: workspaceId,\n },\n message: workspaceId\n ? `Found ${api_keys.length} API key(s) for workspace ${workspaceId}`\n : `Found ${api_keys.length} API key(s) in the organization`,\n };\n } catch (error) {\n return {\n success: false,\n error:\n error instanceof Error ? error.message : 'Failed to list API keys',\n api_keys: [],\n };\n }\n },\n});\n\nexport default listAPIKeys;\n",
|
|
212
|
+
"tools/get-organization.ts": "import { tool } from 'veryfront/tool';\nimport { z } from 'zod';\nimport { getAnthropicAdminClient } from '../../lib/anthropic-admin-client';\n\nexport const getOrganization = tool({\n name: 'get_organization',\n description:\n 'Get detailed information about the Anthropic organization including name, settings, default configurations, and billing information.',\n parameters: z.object({}),\n execute: async () => {\n try {\n const client = getAnthropicAdminClient();\n const organization = await client.getOrganization();\n\n return {\n success: true,\n organization,\n message: `Retrieved organization details for ${organization.display_name}`,\n };\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : 'Failed to retrieve organization details';\n\n return {\n success: false,\n error: message,\n organization: null,\n };\n }\n },\n});\n\nexport default getOrganization;\n",
|
|
213
|
+
"tools/list-members.ts": "import { tool } from 'veryfront/tool';\nimport { z } from 'zod';\nimport { getAnthropicAdminClient } from '../../lib/anthropic-admin-client';\n\nexport const listMembers = tool({\n name: 'list_members',\n description:\n 'List all members in the Anthropic organization. Returns member details including email, role, status, and activity information.',\n parameters: z.object({}),\n execute: async () => {\n try {\n const client = getAnthropicAdminClient();\n const { members } = await client.listMembers();\n\n const membersByRole = members.reduce<Record<string, number>>(\n (acc, member) => {\n acc[member.role] = (acc[member.role] ?? 0) + 1;\n return acc;\n },\n {}\n );\n\n const membersByStatus = members.reduce<Record<string, number>>(\n (acc, member) => {\n acc[member.status] = (acc[member.status] ?? 0) + 1;\n return acc;\n },\n {}\n );\n\n let active = 0;\n let pending = 0;\n\n for (const member of members) {\n if (member.status === 'active') active += 1;\n if (member.status === 'pending') pending += 1;\n }\n\n return {\n success: true,\n members,\n summary: {\n total: members.length,\n active,\n pending,\n by_role: membersByRole,\n by_status: membersByStatus,\n },\n message: `Found ${members.length} member(s) in the organization`,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to list members',\n members: [],\n };\n }\n },\n});\n\nexport default listMembers;\n",
|
|
214
|
+
".env.example": "# Anthropic Admin API Configuration\n# Get your admin API key from https://console.anthropic.com\n# Admin keys provide full access to organization management features\nANTHROPIC_ADMIN_API_KEY=sk-ant-admin-your-api-key-here\n"
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
"integration:stripe": {
|
|
218
|
+
"files": {
|
|
219
|
+
"lib/stripe-client.ts": "import { getApiKey } from \"./token-store.ts\";\n\nconst STRIPE_API_VERSION = \"2024-12-18.acacia\";\nconst STRIPE_BASE_URL = \"https://api.stripe.com/v1\";\n\nexport interface StripeCustomer {\n id: string;\n object: \"customer\";\n email: string | null;\n name: string | null;\n description: string | null;\n created: number;\n metadata: Record<string, string>;\n balance: number;\n currency: string | null;\n default_source: string | null;\n}\n\nexport interface StripePaymentIntent {\n id: string;\n object: \"payment_intent\";\n amount: number;\n currency: string;\n status:\n | \"requires_payment_method\"\n | \"requires_confirmation\"\n | \"requires_action\"\n | \"processing\"\n | \"requires_capture\"\n | \"canceled\"\n | \"succeeded\";\n customer: string | null;\n description: string | null;\n created: number;\n metadata: Record<string, string>;\n receipt_email: string | null;\n}\n\nexport interface StripeSubscription {\n id: string;\n object: \"subscription\";\n customer: string;\n status:\n | \"incomplete\"\n | \"incomplete_expired\"\n | \"trialing\"\n | \"active\"\n | \"past_due\"\n | \"canceled\"\n | \"unpaid\"\n | \"paused\";\n current_period_start: number;\n current_period_end: number;\n created: number;\n canceled_at: number | null;\n metadata: Record<string, string>;\n items: {\n data: Array<{\n id: string;\n price: {\n id: string;\n unit_amount: number;\n currency: string;\n recurring: { interval: string; interval_count: number };\n };\n }>;\n };\n}\n\nexport interface StripeBalance {\n object: \"balance\";\n available: Array<{ amount: number; currency: string; source_types?: Record<string, number> }>;\n pending: Array<{ amount: number; currency: string; source_types?: Record<string, number> }>;\n livemode: boolean;\n}\n\nexport interface StripeBalanceTransaction {\n id: string;\n object: \"balance_transaction\";\n amount: number;\n currency: string;\n description: string | null;\n fee: number;\n net: number;\n status: \"available\" | \"pending\";\n type: string;\n created: number;\n}\n\ninterface StripeListResponse<T> {\n object: \"list\";\n data: T[];\n has_more: boolean;\n url: string;\n}\n\ninterface StripeError {\n error: {\n message: string;\n type: string;\n code?: string;\n param?: string;\n };\n}\n\nfunction addCreatedParams(\n params: Record<string, string | number>,\n created?: { gt?: number; gte?: number; lt?: number; lte?: number },\n): void {\n if (!created) return;\n\n if (created.gt) params[\"created[gt]\"] = created.gt;\n if (created.gte) params[\"created[gte]\"] = created.gte;\n if (created.lt) params[\"created[lt]\"] = created.lt;\n if (created.lte) params[\"created[lte]\"] = created.lte;\n}\n\nfunction flattenToFormData(\n formData: URLSearchParams,\n obj: Record<string, unknown>,\n prefix = \"\",\n): void {\n for (const [key, value] of Object.entries(obj)) {\n const formKey = prefix ? `${prefix}[${key}]` : key;\n\n if (value != null && typeof value === \"object\" && !Array.isArray(value)) {\n flattenToFormData(formData, value as Record<string, unknown>, formKey);\n continue;\n }\n\n if (value != null) formData.append(formKey, String(value));\n }\n}\n\nasync function stripeFetch<T>(\n endpoint: string,\n options: RequestInit & { params?: Record<string, string | number | boolean> } = {},\n): Promise<T> {\n const apiKey = getApiKey();\n if (!apiKey) throw new Error(\"Not authenticated with Stripe. Please set STRIPE_SECRET_KEY.\");\n\n let url = `${STRIPE_BASE_URL}${endpoint}`;\n if (options.params) {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(options.params)) {\n params.append(key, String(value));\n }\n url += `?${params.toString()}`;\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${apiKey}`,\n \"Stripe-Version\": STRIPE_API_VERSION,\n ...(options.headers as Record<string, string> | undefined),\n };\n\n let body = options.body;\n if (options.method === \"POST\" && typeof options.body === \"string\") {\n try {\n const jsonBody = JSON.parse(options.body) as Record<string, unknown>;\n const formData = new URLSearchParams();\n flattenToFormData(formData, jsonBody);\n body = formData.toString();\n headers[\"Content-Type\"] = \"application/x-www-form-urlencoded\";\n } catch {\n // If not JSON, use as-is\n }\n }\n\n const response = await fetch(url, { ...options, headers, body });\n const data = await response.json();\n\n if (!response.ok) {\n const error = data as StripeError;\n throw new Error(`Stripe API error: ${response.status} ${error.error?.message || response.statusText}`);\n }\n\n return data as T;\n}\n\nexport async function listCustomers(options?: {\n limit?: number;\n email?: string;\n created?: { gt?: number; gte?: number; lt?: number; lte?: number };\n}): Promise<StripeCustomer[]> {\n const params: Record<string, string | number> = { limit: options?.limit || 10 };\n\n if (options?.email) params.email = options.email;\n addCreatedParams(params, options?.created);\n\n const response = await stripeFetch<StripeListResponse<StripeCustomer>>(\"/customers\", { params });\n return response.data;\n}\n\nexport function getCustomer(customerId: string): Promise<StripeCustomer> {\n return stripeFetch<StripeCustomer>(`/customers/${customerId}`);\n}\n\nexport function createCustomer(data: {\n email?: string;\n name?: string;\n description?: string;\n metadata?: Record<string, string>;\n}): Promise<StripeCustomer> {\n return stripeFetch<StripeCustomer>(\"/customers\", { method: \"POST\", body: JSON.stringify(data) });\n}\n\nexport function updateCustomer(\n customerId: string,\n data: {\n email?: string;\n name?: string;\n description?: string;\n metadata?: Record<string, string>;\n },\n): Promise<StripeCustomer> {\n return stripeFetch<StripeCustomer>(`/customers/${customerId}`, {\n method: \"POST\",\n body: JSON.stringify(data),\n });\n}\n\nexport async function listPaymentIntents(options?: {\n limit?: number;\n customer?: string;\n created?: { gt?: number; gte?: number; lt?: number; lte?: number };\n}): Promise<StripePaymentIntent[]> {\n const params: Record<string, string | number> = { limit: options?.limit || 10 };\n\n if (options?.customer) params.customer = options.customer;\n addCreatedParams(params, options?.created);\n\n const response = await stripeFetch<StripeListResponse<StripePaymentIntent>>(\"/payment_intents\", { params });\n return response.data;\n}\n\nexport function getPaymentIntent(paymentIntentId: string): Promise<StripePaymentIntent> {\n return stripeFetch<StripePaymentIntent>(`/payment_intents/${paymentIntentId}`);\n}\n\nexport function createPaymentIntent(data: {\n amount: number;\n currency: string;\n customer?: string;\n description?: string;\n metadata?: Record<string, string>;\n payment_method?: string;\n confirm?: boolean;\n}): Promise<StripePaymentIntent> {\n return stripeFetch<StripePaymentIntent>(\"/payment_intents\", { method: \"POST\", body: JSON.stringify(data) });\n}\n\nexport async function listSubscriptions(options?: {\n limit?: number;\n customer?: string;\n status?:\n | \"incomplete\"\n | \"incomplete_expired\"\n | \"trialing\"\n | \"active\"\n | \"past_due\"\n | \"canceled\"\n | \"unpaid\"\n | \"paused\";\n created?: { gt?: number; gte?: number; lt?: number; lte?: number };\n}): Promise<StripeSubscription[]> {\n const params: Record<string, string | number> = { limit: options?.limit || 10 };\n\n if (options?.customer) params.customer = options.customer;\n if (options?.status) params.status = options.status;\n addCreatedParams(params, options?.created);\n\n const response = await stripeFetch<StripeListResponse<StripeSubscription>>(\"/subscriptions\", { params });\n return response.data;\n}\n\nexport function getSubscription(subscriptionId: string): Promise<StripeSubscription> {\n return stripeFetch<StripeSubscription>(`/subscriptions/${subscriptionId}`);\n}\n\nexport function getBalance(): Promise<StripeBalance> {\n return stripeFetch<StripeBalance>(\"/balance\");\n}\n\nexport async function listBalanceTransactions(options?: {\n limit?: number;\n created?: { gt?: number; gte?: number; lt?: number; lte?: number };\n type?: string;\n}): Promise<StripeBalanceTransaction[]> {\n const params: Record<string, string | number> = { limit: options?.limit || 10 };\n\n addCreatedParams(params, options?.created);\n if (options?.type) params.type = options.type;\n\n const response = await stripeFetch<StripeListResponse<StripeBalanceTransaction>>(\"/balance_transactions\", { params });\n return response.data;\n}\n\nexport function formatAmount(amount: number, currency: string): string {\n return new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency: currency.toUpperCase() }).format(amount / 100);\n}\n\nexport function formatDate(timestamp: number): string {\n return new Date(timestamp * 1000).toISOString();\n}\n",
|
|
220
|
+
"tools/list-subscriptions.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatAmount, formatDate, listSubscriptions } from \"../../lib/stripe-client.ts\";\n\nexport default tool({\n id: \"list-subscriptions\",\n description:\n \"List Stripe subscriptions. Supports filtering by customer, status, and creation date range.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(10)\n .describe(\"Maximum number of subscriptions to retrieve\"),\n customerId: z.string().optional().describe(\"Filter by customer ID (starts with cus_)\"),\n status: z\n .enum([\n \"incomplete\",\n \"incomplete_expired\",\n \"trialing\",\n \"active\",\n \"past_due\",\n \"canceled\",\n \"unpaid\",\n \"paused\",\n ])\n .optional()\n .describe(\"Filter by subscription status\"),\n createdAfter: z\n .number()\n .optional()\n .describe(\"Filter subscriptions created after this Unix timestamp\"),\n createdBefore: z\n .number()\n .optional()\n .describe(\"Filter subscriptions created before this Unix timestamp\"),\n }),\n async execute({ limit, customerId, status, createdAfter, createdBefore }) {\n const created =\n createdAfter || createdBefore ? { gte: createdAfter, lte: createdBefore } : undefined;\n\n const subscriptions = await listSubscriptions({\n limit,\n customer: customerId,\n status,\n created,\n });\n\n return subscriptions.map((subscription) => ({\n id: subscription.id,\n customer: subscription.customer,\n status: subscription.status,\n currentPeriodStart: formatDate(subscription.current_period_start),\n currentPeriodEnd: formatDate(subscription.current_period_end),\n created: formatDate(subscription.created),\n canceledAt: subscription.canceled_at ? formatDate(subscription.canceled_at) : null,\n items: subscription.items.data.map((item) => ({\n id: item.id,\n priceId: item.price.id,\n amount: formatAmount(item.price.unit_amount, item.price.currency),\n amountRaw: item.price.unit_amount,\n currency: item.price.currency,\n interval: item.price.recurring.interval,\n intervalCount: item.price.recurring.interval_count,\n })),\n metadata: subscription.metadata,\n }));\n },\n});\n",
|
|
221
|
+
"tools/list-payments.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatAmount, formatDate, listPaymentIntents } from \"../../lib/stripe-client.ts\";\n\nexport default tool({\n id: \"list-payments\",\n description: \"List Stripe payment intents. Supports filtering by customer and creation date range.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(10)\n .describe(\"Maximum number of payment intents to retrieve\"),\n customerId: z.string().optional().describe(\"Filter by customer ID (starts with cus_)\"),\n createdAfter: z.number().optional().describe(\"Filter payments created after this Unix timestamp\"),\n createdBefore: z\n .number()\n .optional()\n .describe(\"Filter payments created before this Unix timestamp\"),\n }),\n async execute({ limit, customerId, createdAfter, createdBefore }) {\n const created =\n createdAfter || createdBefore ? { gte: createdAfter, lte: createdBefore } : undefined;\n\n const payments = await listPaymentIntents({\n limit,\n customer: customerId,\n created,\n });\n\n return payments.map((payment) => ({\n id: payment.id,\n amount: formatAmount(payment.amount, payment.currency),\n amountRaw: payment.amount,\n currency: payment.currency,\n status: payment.status,\n customer: payment.customer,\n description: payment.description,\n receiptEmail: payment.receipt_email,\n created: formatDate(payment.created),\n metadata: payment.metadata,\n }));\n },\n});\n",
|
|
222
|
+
"tools/get-balance.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatAmount, getBalance } from \"../../lib/stripe-client.ts\";\n\nfunction mapBalanceItem(bal: {\n amount: number;\n currency: string;\n source_types: unknown;\n}): {\n amount: string;\n amountRaw: number;\n currency: string;\n sourceTypes: unknown;\n} {\n return {\n amount: formatAmount(bal.amount, bal.currency),\n amountRaw: bal.amount,\n currency: bal.currency,\n sourceTypes: bal.source_types,\n };\n}\n\nexport default tool({\n id: \"get-balance\",\n description: \"Retrieve the current Stripe account balance including available and pending funds.\",\n inputSchema: z.object({}),\n async execute() {\n const balance = await getBalance();\n\n return {\n livemode: balance.livemode,\n available: balance.available.map(mapBalanceItem),\n pending: balance.pending.map(mapBalanceItem),\n };\n },\n});\n",
|
|
223
|
+
"tools/list-customers.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatDate, listCustomers } from \"../../lib/stripe-client.ts\";\n\nexport default tool({\n id: \"list-customers\",\n description: \"List Stripe customers. Supports filtering by email and creation date range.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(10)\n .describe(\"Maximum number of customers to retrieve\"),\n email: z.string().email().optional().describe(\"Filter by customer email address\"),\n createdAfter: z\n .number()\n .optional()\n .describe(\"Filter customers created after this Unix timestamp\"),\n createdBefore: z\n .number()\n .optional()\n .describe(\"Filter customers created before this Unix timestamp\"),\n }),\n async execute({ limit, email, createdAfter, createdBefore }) {\n const created =\n createdAfter || createdBefore ? { gte: createdAfter, lte: createdBefore } : undefined;\n\n const customers = await listCustomers({ limit, email, created });\n\n return customers.map((customer) => ({\n id: customer.id,\n email: customer.email,\n name: customer.name,\n description: customer.description,\n created: formatDate(customer.created),\n balance: customer.balance,\n currency: customer.currency,\n metadata: customer.metadata,\n }));\n },\n});\n",
|
|
224
|
+
"tools/get-customer.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatDate, getCustomer } from \"../../lib/stripe-client.ts\";\n\nexport default tool({\n id: \"get-customer\",\n description: \"Retrieve detailed information about a specific Stripe customer by their ID.\",\n inputSchema: z.object({\n customerId: z.string().describe(\"The Stripe customer ID (starts with cus_)\"),\n }),\n async execute({ customerId }) {\n const customer = await getCustomer(customerId);\n\n return {\n id: customer.id,\n email: customer.email,\n name: customer.name,\n description: customer.description,\n created: formatDate(customer.created),\n balance: customer.balance,\n currency: customer.currency,\n defaultSource: customer.default_source,\n metadata: customer.metadata,\n };\n },\n});\n",
|
|
225
|
+
"app/api/auth/stripe/route.ts": "import { setApiKey } from \"../../../../lib/token-store.ts\";\n\nexport async function POST(request: Request): Promise<Response> {\n let body: unknown;\n\n try {\n body = await request.json();\n } catch {\n return Response.json({ error: \"Internal server error\" }, { status: 500 });\n }\n\n const apiKey = (body as { apiKey?: unknown })?.apiKey;\n\n if (typeof apiKey !== \"string\" || !apiKey) {\n return Response.json({ error: \"API key is required\" }, { status: 400 });\n }\n\n if (!apiKey.startsWith(\"sk_test_\") && !apiKey.startsWith(\"sk_live_\")) {\n return Response.json(\n {\n error:\n \"Invalid Stripe API key format. Key should start with sk_test_ or sk_live_\",\n },\n { status: 400 },\n );\n }\n\n try {\n const response = await fetch(\"https://api.stripe.com/v1/balance\", {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Stripe-Version\": \"2024-12-18.acacia\",\n },\n });\n\n if (!response.ok) {\n const error = await response.json();\n return Response.json(\n {\n error: `Invalid API key: ${error.error?.message ?? \"Authentication failed\"}`,\n },\n { status: 401 },\n );\n }\n } catch {\n return Response.json(\n { error: \"Failed to validate API key with Stripe\" },\n { status: 500 },\n );\n }\n\n setApiKey(apiKey);\n\n return Response.json({\n success: true,\n message: \"Stripe API key validated and stored successfully\",\n });\n}\n\nexport function GET(): Response {\n const apiKey = process.env.STRIPE_SECRET_KEY;\n\n return Response.json({\n authenticated: !!apiKey,\n hasEnvKey: !!apiKey,\n });\n}\n",
|
|
226
|
+
".env.example": "# Stripe Integration\n# Get your API keys at https://dashboard.stripe.com/apikeys\n# Use test keys (sk_test_...) for development, live keys (sk_live_...) for production\n\nSTRIPE_SECRET_KEY=sk_test_your_secret_key_here\nSTRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here\n"
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
"integration:twitter": {
|
|
230
|
+
"files": {
|
|
231
|
+
"lib/twitter-client.ts": "/**\n * Twitter API Client\n *\n * Provides a type-safe interface to Twitter API v2 operations.\n */\n\nimport { getValidToken } from \"./oauth.ts\";\n\n// Helper for Cross-Platform environment access\nfunction getEnv(key: string): string | undefined {\n // @ts-ignore - Deno global\n if (typeof Deno !== \"undefined\") return Deno.env.get(key);\n // @ts-ignore - process global\n if (typeof process !== \"undefined\" && process.env) return process.env[key];\n return undefined;\n}\n\nconst TWITTER_API_BASE = \"https://api.twitter.com/2\";\n\nexport interface TwitterUser {\n id: string;\n name: string;\n username: string;\n created_at?: string;\n description?: string;\n location?: string;\n profile_image_url?: string;\n public_metrics?: {\n followers_count: number;\n following_count: number;\n tweet_count: number;\n listed_count: number;\n };\n verified?: boolean;\n}\n\nexport interface Tweet {\n id: string;\n text: string;\n author_id?: string;\n created_at?: string;\n public_metrics?: {\n retweet_count: number;\n reply_count: number;\n like_count: number;\n quote_count: number;\n };\n referenced_tweets?: Array<{\n type: string;\n id: string;\n }>;\n entities?: {\n hashtags?: Array<{ tag: string }>;\n mentions?: Array<{ username: string; id: string }>;\n urls?: Array<{ url: string; expanded_url: string }>;\n };\n}\n\nexport interface SearchResult {\n data: Tweet[];\n meta: {\n newest_id?: string;\n oldest_id?: string;\n result_count: number;\n next_token?: string;\n };\n}\n\n/**\n * Twitter OAuth provider configuration\n */\nexport const twitterOAuthProvider = {\n name: \"twitter\",\n authorizationUrl: \"https://twitter.com/i/oauth2/authorize\",\n tokenUrl: \"https://api.twitter.com/2/oauth2/token\",\n clientId: getEnv(\"TWITTER_CLIENT_ID\") ?? \"\",\n clientSecret: getEnv(\"TWITTER_CLIENT_SECRET\") ?? \"\",\n scopes: [\n \"tweet.read\",\n \"tweet.write\",\n \"users.read\",\n \"follows.read\",\n \"offline.access\",\n ],\n callbackPath: \"/api/auth/twitter/callback\",\n usePKCE: true,\n};\n\n/**\n * Create a Twitter client for a specific user\n */\nexport function createTwitterClient(userId: string): {\n getMe: () => Promise<TwitterUser>;\n getUserById: (userId: string) => Promise<TwitterUser>;\n getTweets: (\n userId: string,\n options?: { maxResults?: number; excludeReplies?: boolean },\n ) => Promise<Tweet[]>;\n getTweet: (tweetId: string) => Promise<Tweet>;\n postTweet: (text: string) => Promise<{ id: string; text: string }>;\n searchTweets: (\n query: string,\n options?: { maxResults?: number; sortOrder?: \"recency\" | \"relevancy\" },\n ) => Promise<SearchResult>;\n getTimeline: (options?: { maxResults?: number }) => Promise<Tweet[]>;\n} {\n async function getAccessToken(): Promise<string> {\n const token = await getValidToken(twitterOAuthProvider, userId, \"twitter\");\n if (!token) {\n throw new Error(\n \"Twitter not connected. Please connect your Twitter account first.\",\n );\n }\n return token;\n }\n\n async function twitterFetch<T>(\n endpoint: string,\n options: RequestInit = {},\n ): Promise<T> {\n const accessToken = await getAccessToken();\n\n const response = await fetch(`${TWITTER_API_BASE}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ detail: response.statusText }));\n throw new Error(\n `Twitter API error: ${error.detail || error.title || response.statusText}`,\n );\n }\n\n return response.json();\n }\n\n return {\n /**\n * Get authenticated user information\n */\n async getMe(): Promise<TwitterUser> {\n const result = await twitterFetch<{ data: TwitterUser }>(\n \"/users/me?user.fields=created_at,description,location,profile_image_url,public_metrics,verified\",\n );\n return result.data;\n },\n\n /**\n * Get user by ID\n */\n async getUserById(userId: string): Promise<TwitterUser> {\n const result = await twitterFetch<{ data: TwitterUser }>(\n `/users/${userId}?user.fields=created_at,description,location,profile_image_url,public_metrics,verified`,\n );\n return result.data;\n },\n\n /**\n * Get user's tweets\n */\n async getTweets(\n userId: string,\n options: { maxResults?: number; excludeReplies?: boolean } = {},\n ): Promise<Tweet[]> {\n const params = new URLSearchParams({\n max_results: String(options.maxResults ?? 10),\n \"tweet.fields\": \"created_at,public_metrics,referenced_tweets,entities\",\n });\n\n if (options.excludeReplies) params.set(\"exclude\", \"replies\");\n\n const result = await twitterFetch<{ data: Tweet[] }>(\n `/users/${userId}/tweets?${params.toString()}`,\n );\n return result.data ?? [];\n },\n\n /**\n * Get single tweet by ID\n */\n async getTweet(tweetId: string): Promise<Tweet> {\n const result = await twitterFetch<{ data: Tweet }>(\n `/tweets/${tweetId}?tweet.fields=created_at,public_metrics,referenced_tweets,entities,author_id`,\n );\n return result.data;\n },\n\n /**\n * Post a new tweet\n */\n async postTweet(text: string): Promise<{ id: string; text: string }> {\n const result = await twitterFetch<{ data: { id: string; text: string } }>(\n \"/tweets\",\n {\n method: \"POST\",\n body: JSON.stringify({ text }),\n },\n );\n return result.data;\n },\n\n /**\n * Search recent tweets\n */\n searchTweets(\n query: string,\n options: { maxResults?: number; sortOrder?: \"recency\" | \"relevancy\" } = {},\n ): Promise<SearchResult> {\n const params = new URLSearchParams({\n query,\n max_results: String(options.maxResults ?? 10),\n \"tweet.fields\":\n \"created_at,public_metrics,referenced_tweets,entities,author_id\",\n sort_order: options.sortOrder ?? \"recency\",\n });\n\n return twitterFetch<SearchResult>(\n `/tweets/search/recent?${params.toString()}`,\n );\n },\n\n /**\n * Get home timeline (reverse chronological tweets from followed accounts)\n */\n async getTimeline(options: { maxResults?: number } = {}): Promise<Tweet[]> {\n const me = await twitterFetch<{ data: TwitterUser }>(\"/users/me\");\n const timelineUserId = me.data.id;\n\n const params = new URLSearchParams({\n max_results: String(options.maxResults ?? 10),\n \"tweet.fields\":\n \"created_at,public_metrics,referenced_tweets,entities,author_id\",\n });\n\n const result = await twitterFetch<{ data: Tweet[] }>(\n `/users/${timelineUserId}/timelines/reverse_chronological?${params.toString()}`,\n );\n return result.data ?? [];\n },\n };\n}\n\nexport type TwitterClient = ReturnType<typeof createTwitterClient>;\n",
|
|
232
|
+
"tools/get-timeline.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createTwitterClient } from \"../../lib/twitter-client.ts\";\n\nexport default tool({\n id: \"get-timeline\",\n description: \"Get the authenticated user's home timeline (tweets from followed accounts)\",\n inputSchema: z.object({\n maxResults: z\n .number()\n .min(1)\n .max(100)\n .optional()\n .describe(\"Maximum number of tweets to return (default: 10)\"),\n }),\n execute: async ({ maxResults }, context) => {\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const twitter = createTwitterClient(userId);\n const tweets = await twitter.getTimeline({ maxResults: maxResults ?? 10 });\n\n if (!tweets?.length) {\n return {\n success: true,\n tweets: [],\n count: 0,\n message: \"No tweets found in your timeline.\",\n };\n }\n\n return {\n success: true,\n tweets: tweets.map((tweet) => ({\n id: tweet.id,\n text: tweet.text,\n author_id: tweet.author_id,\n created_at: tweet.created_at,\n metrics: tweet.public_metrics,\n hashtags: tweet.entities?.hashtags?.map((h) => h.tag),\n mentions: tweet.entities?.mentions?.map((m) => m.username),\n })),\n count: tweets.length,\n message: `Retrieved ${tweets.length} tweets from your timeline.`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Twitter not connected. Please connect your Twitter account.\",\n connectUrl: \"/api/auth/twitter\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
233
|
+
"tools/post-tweet.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createTwitterClient } from \"../../lib/twitter-client.ts\";\n\nexport default tool({\n id: \"post-tweet\",\n description: \"Post a new tweet to Twitter/X. Maximum length is 280 characters.\",\n inputSchema: z.object({\n text: z\n .string()\n .min(1)\n .max(280)\n .describe(\"Tweet text content (max 280 characters)\"),\n }),\n execute: async ({ text }, context) => {\n const userId = context?.userId ?? \"current-user\";\n\n if (text.length > 280) {\n return {\n error: \"Tweet text exceeds 280 character limit\",\n length: text.length,\n maxLength: 280,\n };\n }\n\n try {\n const twitter = createTwitterClient(userId);\n const result = await twitter.postTweet(text);\n\n return {\n success: true,\n tweetId: result.id,\n text: result.text,\n message: \"Tweet posted successfully!\",\n url: `https://twitter.com/i/web/status/${result.id}`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Twitter not connected. Please connect your Twitter account.\",\n connectUrl: \"/api/auth/twitter\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
234
|
+
"tools/search-tweets.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createTwitterClient } from \"../../lib/twitter-client.ts\";\n\nexport default tool({\n id: \"search-tweets\",\n description:\n \"Search recent tweets on Twitter/X. Returns up to 10 recent tweets matching the query.\",\n inputSchema: z.object({\n query: z\n .string()\n .min(1)\n .describe(\n \"Search query (supports Twitter search operators like 'from:', 'to:', '#hashtag', etc.)\",\n ),\n maxResults: z\n .number()\n .min(1)\n .max(100)\n .optional()\n .describe(\"Maximum number of tweets to return (default: 10)\"),\n sortOrder: z\n .enum([\"recency\", \"relevancy\"])\n .optional()\n .describe(\"Sort order: 'recency' (default) or 'relevancy'\"),\n }),\n execute: async ({ query, maxResults, sortOrder }, context) => {\n // Default to \"current-user\" for development; in production, always pass userId from session\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const twitter = createTwitterClient(userId);\n const result = await twitter.searchTweets(query, {\n maxResults: maxResults ?? 10,\n sortOrder: sortOrder ?? \"recency\",\n });\n\n const tweets = result.data ?? [];\n\n if (tweets.length === 0) {\n return {\n success: true,\n tweets: [],\n count: 0,\n message: `No tweets found matching query: \"${query}\"`,\n };\n }\n\n return {\n success: true,\n tweets: tweets.map((tweet) => ({\n id: tweet.id,\n text: tweet.text,\n author_id: tweet.author_id,\n created_at: tweet.created_at,\n metrics: tweet.public_metrics,\n hashtags: tweet.entities?.hashtags?.map((h) => h.tag),\n mentions: tweet.entities?.mentions?.map((m) => m.username),\n })),\n count: result.meta.result_count,\n message: `Found ${result.meta.result_count} tweets matching query: \"${query}\"`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Twitter not connected. Please connect your Twitter account.\",\n connectUrl: \"/api/auth/twitter\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
235
|
+
"app/api/auth/twitter/route.ts": "import { createOAuthInitHandler, memoryTokenStore, twitterConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(twitterConfig, { tokenStore: memoryTokenStore });\n",
|
|
236
|
+
"app/api/auth/twitter/callback/route.ts": "import { createOAuthCallbackHandler, memoryTokenStore, twitterConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(twitterConfig, { tokenStore: hybridTokenStore });\n",
|
|
237
|
+
".env.example": "# Twitter / X Integration\n# Create an app at https://developer.twitter.com/en/portal/dashboard\n# Enable OAuth 2.0 and add callback URL: http://localhost:3000/api/auth/twitter/callback\n\nTWITTER_CLIENT_ID=your_client_id_here\nTWITTER_CLIENT_SECRET=your_client_secret_here\n"
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
"integration:airtable": {
|
|
241
|
+
"files": {
|
|
242
|
+
"lib/airtable-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst AIRTABLE_BASE_URL = \"https://api.airtable.com/v0\";\nconst AIRTABLE_META_BASE_URL = \"https://api.airtable.com/v0/meta\";\n\ninterface AirtableResponse<T> {\n records?: T[];\n offset?: string;\n}\n\ninterface AirtableBase {\n id: string;\n name: string;\n permissionLevel: string;\n}\n\ninterface AirtableBaseSchema {\n tables: Array<{\n id: string;\n name: string;\n primaryFieldId: string;\n fields: Array<{\n id: string;\n name: string;\n type: string;\n options?: Record<string, unknown>;\n }>;\n views: Array<{\n id: string;\n name: string;\n type: string;\n }>;\n }>;\n}\n\nexport interface AirtableRecord {\n id: string;\n createdTime: string;\n fields: Record<string, unknown>;\n}\n\nfunction getTokenOrThrow(): string {\n const token = getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Airtable. Please connect your account.\");\n }\n return token;\n}\n\nasync function apiFetch<T>(\n baseUrl: string,\n endpoint: string,\n options: RequestInit = {},\n errorPrefix: string,\n): Promise<T> {\n const token = getTokenOrThrow();\n\n const response = await fetch(`${baseUrl}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as any));\n throw new Error(\n `${errorPrefix}: ${response.status} ${error?.error?.message ?? response.statusText}`,\n );\n }\n\n return response.json() as Promise<T>;\n}\n\nasync function airtableFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n return apiFetch<T>(AIRTABLE_BASE_URL, endpoint, options, \"Airtable API error\");\n}\n\nasync function metaFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n return apiFetch<T>(AIRTABLE_META_BASE_URL, endpoint, options, \"Airtable Meta API error\");\n}\n\nexport async function listBases(): Promise<AirtableBase[]> {\n const response = await metaFetch<{ bases: AirtableBase[] }>(\"/bases\");\n return response.bases ?? [];\n}\n\nexport async function getBase(baseId: string): Promise<AirtableBaseSchema> {\n return metaFetch<AirtableBaseSchema>(`/bases/${baseId}/tables`);\n}\n\nexport async function listRecords(\n baseId: string,\n tableIdOrName: string,\n options?: {\n fields?: string[];\n filterByFormula?: string;\n maxRecords?: number;\n pageSize?: number;\n sort?: Array<{ field: string; direction: \"asc\" | \"desc\" }>;\n view?: string;\n offset?: string;\n },\n): Promise<{ records: AirtableRecord[]; offset?: string }> {\n const params = new URLSearchParams();\n\n options?.fields?.forEach((field) => params.append(\"fields[]\", field));\n if (options?.filterByFormula) params.append(\"filterByFormula\", options.filterByFormula);\n if (options?.maxRecords) params.append(\"maxRecords\", options.maxRecords.toString());\n if (options?.pageSize) params.append(\"pageSize\", options.pageSize.toString());\n options?.sort?.forEach((s, i) => {\n params.append(`sort[${i}][field]`, s.field);\n params.append(`sort[${i}][direction]`, s.direction);\n });\n if (options?.view) params.append(\"view\", options.view);\n if (options?.offset) params.append(\"offset\", options.offset);\n\n const queryString = params.toString();\n const endpoint = `/${baseId}/${encodeURIComponent(tableIdOrName)}${\n queryString ? `?${queryString}` : \"\"\n }`;\n\n const response = await airtableFetch<AirtableResponse<AirtableRecord>>(endpoint);\n\n return {\n records: response.records ?? [],\n offset: response.offset,\n };\n}\n\nexport function getRecord(\n baseId: string,\n tableIdOrName: string,\n recordId: string,\n): Promise<AirtableRecord> {\n return airtableFetch<AirtableRecord>(\n `/${baseId}/${encodeURIComponent(tableIdOrName)}/${recordId}`,\n );\n}\n\nexport function createRecord(\n baseId: string,\n tableIdOrName: string,\n fields: Record<string, unknown>,\n): Promise<AirtableRecord> {\n return airtableFetch<AirtableRecord>(`/${baseId}/${encodeURIComponent(tableIdOrName)}`, {\n method: \"POST\",\n body: JSON.stringify({ fields }),\n });\n}\n\nexport async function createRecords(\n baseId: string,\n tableIdOrName: string,\n records: Array<{ fields: Record<string, unknown> }>,\n): Promise<AirtableRecord[]> {\n const response = await airtableFetch<{ records: AirtableRecord[] }>(\n `/${baseId}/${encodeURIComponent(tableIdOrName)}`,\n {\n method: \"POST\",\n body: JSON.stringify({ records }),\n },\n );\n return response.records;\n}\n\nexport function updateRecord(\n baseId: string,\n tableIdOrName: string,\n recordId: string,\n fields: Record<string, unknown>,\n options?: { destructive?: boolean },\n): Promise<AirtableRecord> {\n return airtableFetch<AirtableRecord>(\n `/${baseId}/${encodeURIComponent(tableIdOrName)}/${recordId}`,\n {\n method: options?.destructive ? \"PUT\" : \"PATCH\",\n body: JSON.stringify({ fields }),\n },\n );\n}\n\nexport function deleteRecord(\n baseId: string,\n tableIdOrName: string,\n recordId: string,\n): Promise<{ id: string; deleted: boolean }> {\n return airtableFetch<{ id: string; deleted: boolean }>(\n `/${baseId}/${encodeURIComponent(tableIdOrName)}/${recordId}`,\n { method: \"DELETE\" },\n );\n}\n\nexport function formatFieldValue(value: unknown): string {\n if (value == null) return \"\";\n if (Array.isArray(value)) return value.map((v) => formatFieldValue(v)).join(\", \");\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n",
|
|
243
|
+
"tools/list-bases.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listBases } from \"../../lib/airtable-client.ts\";\n\nexport default tool({\n id: \"list-bases\",\n description:\n \"List all accessible Airtable bases in the connected account. Returns base IDs, names, and permission levels.\",\n inputSchema: z.object({}),\n async execute() {\n const bases = await listBases();\n\n return bases.map(({ id, name, permissionLevel }) => ({\n id,\n name,\n permissionLevel,\n }));\n },\n});\n",
|
|
244
|
+
"tools/get-record.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getRecord } from \"../../lib/airtable-client.ts\";\n\nexport default tool({\n id: \"get-record\",\n description:\n \"Get a specific record from an Airtable table by its ID. Returns the full record with all field values.\",\n inputSchema: z.object({\n baseId: z.string().describe('The ID of the Airtable base (starts with \"app\")'),\n tableIdOrName: z.string().describe(\"The ID or name of the table\"),\n recordId: z.string().describe('The ID of the record to retrieve (starts with \"rec\")'),\n }),\n async execute({ baseId, tableIdOrName, recordId }) {\n return getRecord(baseId, tableIdOrName, recordId);\n },\n});\n",
|
|
245
|
+
"tools/create-record.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createRecord } from \"../../lib/airtable-client.ts\";\n\nexport default tool({\n id: \"create-record\",\n description:\n \"Create a new record in an Airtable table. Provide field names and values as an object. Returns the created record with its ID.\",\n inputSchema: z.object({\n baseId: z.string().describe('The ID of the Airtable base (starts with \"app\")'),\n tableIdOrName: z.string().describe(\"The ID or name of the table\"),\n fields: z\n .record(z.unknown())\n .describe(\n 'Object with field names as keys and their values. Field names must match exactly. Example: { \"Name\": \"John Doe\", \"Email\": \"john@example.com\", \"Status\": \"Active\" }',\n ),\n }),\n async execute({ baseId, tableIdOrName, fields }) {\n const { id, createdTime, fields: recordFields } = await createRecord(\n baseId,\n tableIdOrName,\n fields,\n );\n\n return { id, createdTime, fields: recordFields };\n },\n});\n",
|
|
246
|
+
"tools/get-base.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getBase } from \"../../lib/airtable-client.ts\";\n\nexport default tool({\n id: \"get-base\",\n description:\n \"Get the schema and structure of an Airtable base, including all tables, fields, and views. Useful for understanding the data model before querying or creating records.\",\n inputSchema: z.object({\n baseId: z.string().describe('The ID of the Airtable base (starts with \"app\")'),\n }),\n async execute({ baseId }) {\n const { tables } = await getBase(baseId);\n\n return {\n tables: tables.map(({ id, name, primaryFieldId, fields, views }) => ({\n id,\n name,\n primaryFieldId,\n fields: fields.map(({ id, name, type, options }) => ({\n id,\n name,\n type,\n options,\n })),\n views: views.map(({ id, name, type }) => ({\n id,\n name,\n type,\n })),\n })),\n };\n },\n});\n",
|
|
247
|
+
"tools/list-records.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listRecords } from \"../../lib/airtable-client.ts\";\n\nexport default tool({\n id: \"list-records\",\n description:\n \"List records from an Airtable table. Supports filtering with formulas, sorting, and limiting results. Returns record IDs, creation times, and all field values.\",\n inputSchema: z.object({\n baseId: z.string().describe('The ID of the Airtable base (starts with \"app\")'),\n tableIdOrName: z.string().describe(\"The ID or name of the table\"),\n fields: z\n .array(z.string())\n .optional()\n .describe(\"Specific field names to return (returns all fields if not specified)\"),\n filterByFormula: z\n .string()\n .optional()\n .describe('Airtable formula to filter records (e.g., \"{Status} = \\'Done\\'\")'),\n maxRecords: z.number().min(1).max(100).optional().describe(\"Maximum number of records to return\"),\n sort: z\n .array(\n z.object({\n field: z.string().describe(\"Field name to sort by\"),\n direction: z.enum([\"asc\", \"desc\"]).describe(\"Sort direction\"),\n }),\n )\n .optional()\n .describe(\"Array of sort specifications\"),\n view: z.string().optional().describe(\"Name of a view to use for filtering and sorting\"),\n }),\n async execute({ baseId, tableIdOrName, fields, filterByFormula, maxRecords, sort, view }) {\n const { records, offset } = await listRecords(baseId, tableIdOrName, {\n fields,\n filterByFormula,\n maxRecords,\n pageSize: maxRecords,\n sort,\n view,\n });\n\n return {\n records: records.map(({ id, createdTime, fields }) => ({ id, createdTime, fields })),\n count: records.length,\n hasMore: Boolean(offset),\n };\n },\n});\n",
|
|
248
|
+
"app/api/auth/airtable/route.ts": "import { airtableConfig, createOAuthInitHandler, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(airtableConfig, { tokenStore: memoryTokenStore });\n",
|
|
249
|
+
"app/api/auth/airtable/callback/route.ts": "import { airtableConfig, createOAuthCallbackHandler, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(airtableConfig, { tokenStore: hybridTokenStore });\n"
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
"integration:slack": {
|
|
253
|
+
"files": {
|
|
254
|
+
"lib/slack-client.ts": "/**\n * Slack API Client\n *\n * Provides a type-safe interface to Slack API operations.\n */\n\nimport { getValidToken } from \"./oauth.ts\";\n\n// Helper for Cross-Platform environment access\nfunction getEnv(key: string): string | undefined {\n // @ts-ignore - Deno global\n if (typeof Deno !== \"undefined\") {\n // @ts-ignore - Deno global\n return Deno.env.get(key);\n }\n\n // @ts-ignore - process global\n if (typeof process !== \"undefined\" && process.env) {\n // @ts-ignore - process global\n return process.env[key];\n }\n\n return undefined;\n}\n\nconst SLACK_API_BASE = \"https://slack.com/api\";\n\nexport interface SlackChannel {\n id: string;\n name: string;\n is_channel: boolean;\n is_private: boolean;\n is_member: boolean;\n topic?: { value: string };\n purpose?: { value: string };\n}\n\nexport interface SlackMessage {\n type: string;\n user?: string;\n text: string;\n ts: string;\n thread_ts?: string;\n reply_count?: number;\n reactions?: Array<{ name: string; count: number }>;\n}\n\nexport interface SlackUser {\n id: string;\n name: string;\n real_name: string;\n profile: {\n display_name: string;\n email?: string;\n image_48?: string;\n };\n}\n\n/**\n * Slack OAuth provider configuration\n */\nexport const slackOAuthProvider = {\n name: \"slack\",\n authorizationUrl: \"https://slack.com/oauth/v2/authorize\",\n tokenUrl: \"https://slack.com/api/oauth.v2.access\",\n clientId: getEnv(\"SLACK_CLIENT_ID\") ?? \"\",\n clientSecret: getEnv(\"SLACK_CLIENT_SECRET\") ?? \"\",\n scopes: [\n \"channels:history\",\n \"channels:read\",\n \"chat:write\",\n \"users:read\",\n \"im:history\",\n \"im:read\",\n ],\n callbackPath: \"/api/auth/slack/callback\",\n};\n\n/**\n * Create a Slack client for a specific user\n */\nexport function createSlackClient(userId: string): {\n listChannels(options?: {\n limit?: number;\n excludeArchived?: boolean;\n }): Promise<SlackChannel[]>;\n getMessages(\n channelId: string,\n options?: { limit?: number; oldest?: string },\n ): Promise<SlackMessage[]>;\n sendMessage(\n channelId: string,\n text: string,\n options?: { threadTs?: string; unfurlLinks?: boolean },\n ): Promise<{ ts: string; channel: string }>;\n getUser(userId: string): Promise<SlackUser>;\n getThread(channelId: string, threadTs: string): Promise<SlackMessage[]>;\n searchMessages(\n query: string,\n options?: { count?: number },\n ): Promise<SlackMessage[]>;\n} {\n async function getAccessToken(): Promise<string> {\n const token = await getValidToken(slackOAuthProvider, userId, \"slack\");\n if (!token) {\n throw new Error(\n \"Slack not connected. Please connect your Slack account first.\",\n );\n }\n return token;\n }\n\n async function apiRequest<T>(\n method: string,\n params: Record<string, unknown> = {},\n ): Promise<T> {\n const accessToken = await getAccessToken();\n\n const response = await fetch(`${SLACK_API_BASE}/${method}`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json; charset=utf-8\",\n },\n body: JSON.stringify(params),\n });\n\n const data = await response.json();\n\n if (!data.ok) {\n throw new Error(`Slack API error: ${data.error}`);\n }\n\n return data as T;\n }\n\n return {\n /**\n * List channels the user is a member of\n */\n async listChannels(options = {}): Promise<SlackChannel[]> {\n const result = await apiRequest<{ channels: SlackChannel[] }>(\n \"conversations.list\",\n {\n limit: options.limit ?? 100,\n exclude_archived: options.excludeArchived ?? true,\n types: \"public_channel,private_channel\",\n },\n );\n return result.channels;\n },\n\n /**\n * Get messages from a channel\n */\n async getMessages(\n channelId: string,\n options = {},\n ): Promise<SlackMessage[]> {\n const result = await apiRequest<{ messages: SlackMessage[] }>(\n \"conversations.history\",\n {\n channel: channelId,\n limit: options.limit ?? 20,\n oldest: options.oldest,\n },\n );\n return result.messages;\n },\n\n /**\n * Send a message to a channel\n */\n async sendMessage(\n channelId: string,\n text: string,\n options = {},\n ): Promise<{ ts: string; channel: string }> {\n return await apiRequest<{ ts: string; channel: string }>(\n \"chat.postMessage\",\n {\n channel: channelId,\n text,\n thread_ts: options.threadTs,\n unfurl_links: options.unfurlLinks ?? true,\n },\n );\n },\n\n /**\n * Get user info\n */\n async getUser(userId: string): Promise<SlackUser> {\n const result = await apiRequest<{ user: SlackUser }>(\"users.info\", {\n user: userId,\n });\n return result.user;\n },\n\n /**\n * Get thread replies\n */\n async getThread(\n channelId: string,\n threadTs: string,\n ): Promise<SlackMessage[]> {\n const result = await apiRequest<{ messages: SlackMessage[] }>(\n \"conversations.replies\",\n {\n channel: channelId,\n ts: threadTs,\n },\n );\n return result.messages;\n },\n\n /**\n * Search messages\n */\n async searchMessages(\n query: string,\n options = {},\n ): Promise<SlackMessage[]> {\n const result = await apiRequest<{\n messages: { matches: SlackMessage[] };\n }>(\"search.messages\", {\n query,\n count: options.count ?? 20,\n });\n return result.messages.matches;\n },\n };\n}\n\nexport type SlackClient = ReturnType<typeof createSlackClient>;\n",
|
|
255
|
+
"tools/send-message.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createSlackClient } from \"../../lib/slack-client.ts\";\n\nexport default tool({\n id: \"send-message\",\n description: \"Send a message to a Slack channel\",\n inputSchema: z.object({\n channel: z\n .string()\n .describe(\"Channel ID or name (e.g., 'C1234567890' or '#general')\"),\n text: z.string().min(1).describe(\"Message text to send\"),\n threadTs: z\n .string()\n .optional()\n .describe(\"Thread timestamp to reply to (for threaded messages)\"),\n }),\n execute: async ({ channel, text, threadTs }, context) => {\n // Default to \"current-user\" for development; in production, always pass userId from session\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const slack = createSlackClient(userId);\n const result = await slack.sendMessage(channel, text, { threadTs });\n\n const message = threadTs\n ? `Reply sent to thread in ${channel}.`\n : `Message sent to ${channel}.`;\n\n return {\n success: true,\n messageTs: result.ts,\n channel: result.channel,\n message,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Slack not connected. Please connect your Slack account.\",\n connectUrl: \"/api/auth/slack\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
256
|
+
"tools/get-messages.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createSlackClient } from \"../../lib/slack-client.ts\";\n\ntype SlackMessage = {\n text?: string;\n user?: string;\n ts: string;\n thread_ts?: string;\n reply_count?: number;\n reactions?: Array<{ name: string; count: number }>;\n};\n\nexport default tool({\n id: \"get-messages\",\n description: \"Get recent messages from a Slack channel\",\n inputSchema: z.object({\n channel: z.string().describe(\"Channel ID (e.g., 'C1234567890')\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of messages to return\"),\n }),\n execute: async ({ channel, limit }, context) => {\n // Default to \"current-user\" for development; in production, always pass userId from session\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const slack = createSlackClient(userId);\n const messages = await slack.getMessages(channel, { limit });\n\n return {\n messages: messages.map((msg: SlackMessage) => ({\n text: msg.text ?? \"\",\n user: msg.user ?? \"unknown\",\n timestamp: msg.ts,\n threadTs: msg.thread_ts,\n replyCount: msg.reply_count ?? 0,\n reactions: msg.reactions?.map((r) => `${r.name} (${r.count})`) ?? [],\n })),\n count: messages.length,\n channel,\n message: `Retrieved ${messages.length} message(s) from channel.`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Slack not connected. Please connect your Slack account.\",\n connectUrl: \"/api/auth/slack\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
257
|
+
"tools/list-channels.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createSlackClient } from \"../../lib/slack-client.ts\";\n\ntype SlackChannel = {\n id: string;\n name: string;\n is_private: boolean;\n is_member: boolean;\n topic?: { value: string };\n purpose?: { value: string };\n};\n\nexport default tool({\n id: \"list-channels\",\n description: \"List Slack channels the user is a member of\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of channels to return\"),\n excludeArchived: z\n .boolean()\n .default(true)\n .describe(\"Exclude archived channels\"),\n }),\n execute: async ({ limit, excludeArchived }, context) => {\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const slack = createSlackClient(userId);\n const channels = await slack.listChannels({ limit, excludeArchived });\n\n return {\n channels: channels.map((ch: SlackChannel) => ({\n id: ch.id,\n name: ch.name,\n isPrivate: ch.is_private,\n isMember: ch.is_member,\n topic: ch.topic?.value ?? null,\n purpose: ch.purpose?.value ?? null,\n })),\n count: channels.length,\n message: `Found ${channels.length} channel(s).`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Slack not connected. Please connect your Slack account.\",\n connectUrl: \"/api/auth/slack\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
258
|
+
"app/api/auth/slack/route.ts": "import { createOAuthInitHandler, memoryTokenStore, slackConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(slackConfig, { tokenStore: memoryTokenStore });\n",
|
|
259
|
+
"app/api/auth/slack/callback/route.ts": "import { createOAuthCallbackHandler, memoryTokenStore, slackConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(slackConfig, { tokenStore: hybridTokenStore });\n"
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
"integration:trello": {
|
|
263
|
+
"files": {
|
|
264
|
+
"lib/trello-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst TRELLO_BASE_URL = \"https://api.trello.com/1\";\n\ninterface TrelloBoard {\n id: string;\n name: string;\n desc: string;\n closed: boolean;\n url: string;\n prefs: {\n background: string;\n backgroundColor: string;\n };\n dateLastActivity: string;\n}\n\ninterface TrelloList {\n id: string;\n name: string;\n closed: boolean;\n idBoard: string;\n pos: number;\n}\n\ninterface TrelloCard {\n id: string;\n name: string;\n desc: string;\n closed: boolean;\n idBoard: string;\n idList: string;\n idMembers: string[];\n labels: Array<{\n id: string;\n name: string;\n color: string;\n }>;\n due: string | null;\n dueComplete: boolean;\n url: string;\n dateLastActivity: string;\n}\n\ninterface TrelloMember {\n id: string;\n fullName: string;\n username: string;\n avatarUrl: string;\n}\n\nasync function trelloFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Trello. Please connect your account.\");\n }\n\n const clientId = process.env.TRELLO_CLIENT_ID;\n if (!clientId) {\n throw new Error(\"TRELLO_CLIENT_ID environment variable is not set.\");\n }\n\n const url = new URL(`${TRELLO_BASE_URL}${endpoint}`);\n url.searchParams.set(\"key\", clientId);\n url.searchParams.set(\"token\", token);\n\n const response = await fetch(url.toString(), {\n ...options,\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.text().catch(() => \"\");\n throw new Error(`Trello API error: ${response.status} ${error || response.statusText}`);\n }\n\n return response.json();\n}\n\nexport async function listBoards(): Promise<TrelloBoard[]> {\n return trelloFetch<TrelloBoard[]>(\n \"/members/me/boards?fields=name,desc,closed,url,prefs,dateLastActivity\",\n );\n}\n\nexport async function getBoard(boardId: string): Promise<TrelloBoard> {\n return trelloFetch<TrelloBoard>(\n `/boards/${boardId}?fields=name,desc,closed,url,prefs,dateLastActivity`,\n );\n}\n\nexport async function listLists(boardId: string): Promise<TrelloList[]> {\n return trelloFetch<TrelloList[]>(\n `/boards/${boardId}/lists?fields=name,closed,idBoard,pos`,\n );\n}\n\nexport async function listCards(options: {\n boardId?: string;\n listId?: string;\n limit?: number;\n}): Promise<TrelloCard[]> {\n const { boardId, listId, limit = 50 } = options;\n\n if (listId) {\n return trelloFetch<TrelloCard[]>(\n `/lists/${listId}/cards?fields=name,desc,closed,idBoard,idList,idMembers,labels,due,dueComplete,url,dateLastActivity&limit=${limit}`,\n );\n }\n\n if (boardId) {\n return trelloFetch<TrelloCard[]>(\n `/boards/${boardId}/cards?fields=name,desc,closed,idBoard,idList,idMembers,labels,due,dueComplete,url,dateLastActivity&limit=${limit}`,\n );\n }\n\n throw new Error(\"Either boardId or listId must be provided\");\n}\n\nexport async function getCard(cardId: string): Promise<TrelloCard> {\n return trelloFetch<TrelloCard>(\n `/cards/${cardId}?fields=name,desc,closed,idBoard,idList,idMembers,labels,due,dueComplete,url,dateLastActivity`,\n );\n}\n\nexport async function createCard(options: {\n listId: string;\n name: string;\n desc?: string;\n due?: string;\n pos?: string | number;\n idMembers?: string[];\n idLabels?: string[];\n}): Promise<TrelloCard> {\n const params = new URLSearchParams({\n idList: options.listId,\n name: options.name,\n });\n\n if (options.desc) params.set(\"desc\", options.desc);\n if (options.due) params.set(\"due\", options.due);\n if (options.pos !== undefined) params.set(\"pos\", String(options.pos));\n if (options.idMembers) params.set(\"idMembers\", options.idMembers.join(\",\"));\n if (options.idLabels) params.set(\"idLabels\", options.idLabels.join(\",\"));\n\n return trelloFetch<TrelloCard>(`/cards?${params}`, { method: \"POST\" });\n}\n\nexport async function updateCard(\n cardId: string,\n updates: {\n name?: string;\n desc?: string;\n closed?: boolean;\n idList?: string;\n due?: string | null;\n dueComplete?: boolean;\n idMembers?: string[];\n idLabels?: string[];\n pos?: string | number;\n },\n): Promise<TrelloCard> {\n const params = new URLSearchParams();\n\n if (updates.name !== undefined) params.set(\"name\", updates.name);\n if (updates.desc !== undefined) params.set(\"desc\", updates.desc);\n if (updates.closed !== undefined) params.set(\"closed\", String(updates.closed));\n if (updates.idList !== undefined) params.set(\"idList\", updates.idList);\n if (updates.due !== undefined) params.set(\"due\", updates.due ?? \"\");\n if (updates.dueComplete !== undefined) {\n params.set(\"dueComplete\", String(updates.dueComplete));\n }\n if (updates.idMembers !== undefined) params.set(\"idMembers\", updates.idMembers.join(\",\"));\n if (updates.idLabels !== undefined) params.set(\"idLabels\", updates.idLabels.join(\",\"));\n if (updates.pos !== undefined) params.set(\"pos\", String(updates.pos));\n\n return trelloFetch<TrelloCard>(`/cards/${cardId}?${params}`, { method: \"PUT\" });\n}\n\nexport async function getMe(): Promise<TrelloMember> {\n return trelloFetch<TrelloMember>(\"/members/me?fields=fullName,username,avatarUrl\");\n}\n",
|
|
265
|
+
"tools/create-card.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createCard } from \"../../lib/trello-client.ts\";\n\nexport default tool({\n id: \"create-card\",\n description: \"Create a new card in a Trello list.\",\n inputSchema: z.object({\n listId: z.string().describe(\"The ID of the list to create the card in\"),\n name: z.string().describe(\"The name/title of the card\"),\n desc: z.string().optional().describe(\"Description or details for the card\"),\n due: z\n .string()\n .optional()\n .describe(\"Due date in ISO 8601 format (e.g., 2024-12-31T23:59:59.000Z)\"),\n pos: z\n .union([z.string(), z.number()])\n .optional()\n .describe('Position of the card: \"top\", \"bottom\", or a positive number'),\n idMembers: z\n .array(z.string())\n .optional()\n .describe(\"Array of member IDs to assign to the card\"),\n idLabels: z\n .array(z.string())\n .optional()\n .describe(\"Array of label IDs to add to the card\"),\n }),\n async execute({ listId, name, desc, due, pos, idMembers, idLabels }) {\n const card = await createCard({\n listId,\n name,\n desc,\n due,\n pos,\n idMembers,\n idLabels,\n });\n\n return {\n success: true,\n card: {\n id: card.id,\n name: card.name,\n desc: card.desc,\n url: card.url,\n idList: card.idList,\n due: card.due,\n labels: card.labels.map(({ id, name, color }) => ({ id, name, color })),\n },\n };\n },\n});\n",
|
|
266
|
+
"tools/get-card.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getCard } from \"../../lib/trello-client.ts\";\n\nexport default tool({\n id: \"get-card\",\n description: \"Get details of a specific Trello card by its ID.\",\n inputSchema: z.object({\n cardId: z.string().describe(\"The ID of the card to retrieve\"),\n }),\n async execute({ cardId }) {\n const {\n id,\n name,\n desc,\n url,\n closed,\n idList,\n idBoard,\n due,\n dueComplete,\n labels,\n idMembers,\n dateLastActivity,\n } = await getCard(cardId);\n\n return {\n id,\n name,\n desc,\n url,\n closed,\n idList,\n idBoard,\n due,\n dueComplete,\n labels: labels.map(({ id, name, color }) => ({ id, name, color })),\n memberIds: idMembers,\n lastActivity: dateLastActivity,\n };\n },\n});\n",
|
|
267
|
+
"tools/list-cards.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listCards } from \"../../lib/trello-client.ts\";\n\nexport default tool({\n id: \"list-cards\",\n description:\n \"List cards from Trello. Can filter by board or list. Provide either boardId or listId.\",\n inputSchema: z.object({\n boardId: z.string().optional().describe(\"Board ID to list cards from\"),\n listId: z.string().optional().describe(\"List ID to list cards from\"),\n includeArchived: z\n .boolean()\n .default(false)\n .describe(\"Include archived/closed cards\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(50)\n .describe(\"Maximum number of cards to return\"),\n }),\n async execute({ boardId, listId, includeArchived, limit }) {\n if (!boardId && !listId) {\n return { cards: [], message: \"Please specify either a boardId or listId\" };\n }\n\n const cards = await listCards({ boardId, listId, limit });\n\n const visibleCards = includeArchived\n ? cards\n : cards.filter((card) => !card.closed);\n\n return visibleCards.map((card) => ({\n id: card.id,\n name: card.name,\n desc: card.desc,\n url: card.url,\n closed: card.closed,\n idList: card.idList,\n idBoard: card.idBoard,\n due: card.due,\n dueComplete: card.dueComplete,\n labels: card.labels.map((label) => ({\n id: label.id,\n name: label.name,\n color: label.color,\n })),\n memberIds: card.idMembers,\n lastActivity: card.dateLastActivity,\n }));\n },\n});\n",
|
|
268
|
+
"tools/list-boards.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listBoards } from \"../../lib/trello-client.ts\";\n\nexport default tool({\n id: \"list-boards\",\n description: \"List all Trello boards accessible to the current user.\",\n inputSchema: z.object({\n includeArchived: z\n .boolean()\n .default(false)\n .describe(\"Include archived/closed boards\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of boards to return\"),\n }),\n async execute({ includeArchived, limit }) {\n const boards = await listBoards();\n\n const visibleBoards = includeArchived\n ? boards\n : boards.filter((board) => !board.closed);\n\n return visibleBoards.slice(0, limit).map((board) => ({\n id: board.id,\n name: board.name,\n desc: board.desc,\n url: board.url,\n closed: board.closed,\n backgroundColor: board.prefs?.backgroundColor,\n lastActivity: board.dateLastActivity,\n }));\n },\n});\n",
|
|
269
|
+
"tools/update-card.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { updateCard } from \"../../lib/trello-client.ts\";\n\nexport default tool({\n id: \"update-card\",\n description: \"Update an existing Trello card.\",\n inputSchema: z.object({\n cardId: z.string().describe(\"The ID of the card to update\"),\n name: z.string().optional().describe(\"New name/title for the card\"),\n desc: z.string().optional().describe(\"New description or details\"),\n closed: z.boolean().optional().describe(\"Archive or unarchive the card\"),\n idList: z.string().optional().describe(\"Move the card to a different list by list ID\"),\n due: z\n .string()\n .nullable()\n .optional()\n .describe(\"New due date in ISO 8601 format, or null to remove due date\"),\n dueComplete: z.boolean().optional().describe(\"Mark the due date as complete or incomplete\"),\n pos: z\n .union([z.string(), z.number()])\n .optional()\n .describe('New position: \"top\", \"bottom\", or a positive number'),\n idMembers: z\n .array(z.string())\n .optional()\n .describe(\"Array of member IDs to assign to the card (replaces existing)\"),\n idLabels: z\n .array(z.string())\n .optional()\n .describe(\"Array of label IDs for the card (replaces existing)\"),\n }),\n async execute({ cardId, ...updates }) {\n const card = await updateCard(cardId, updates);\n\n return {\n success: true,\n card: {\n id: card.id,\n name: card.name,\n desc: card.desc,\n url: card.url,\n closed: card.closed,\n idList: card.idList,\n due: card.due,\n dueComplete: card.dueComplete,\n labels: card.labels.map(({ id, name, color }) => ({ id, name, color })),\n },\n };\n },\n});\n",
|
|
270
|
+
"app/api/auth/trello/route.ts": "import { createOAuthInitHandler, trelloConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(trelloConfig);\n",
|
|
271
|
+
"app/api/auth/trello/callback/route.ts": "import { createOAuthCallbackHandler, memoryTokenStore, trelloConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState(state: string) {\n return memoryTokenStore.getState(state);\n },\n setState(state: { state: string; codeVerifier?: string; createdAt: number }) {\n return memoryTokenStore.setState(state);\n },\n clearState(state: string) {\n return memoryTokenStore.clearState(state);\n },\n};\n\nexport const GET = createOAuthCallbackHandler(trelloConfig, { tokenStore: hybridTokenStore });\n",
|
|
272
|
+
".env.example": "# Trello OAuth Configuration\n# Get your credentials from https://trello.com/app-key\nTRELLO_CLIENT_ID=your-api-key\nTRELLO_CLIENT_SECRET=your-oauth-secret\n"
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"integration:docs-google": {
|
|
276
|
+
"files": {
|
|
277
|
+
"lib/docs-client.ts": "/**\n * Google Docs API Client\n *\n * Provides a type-safe interface to Google Docs API operations.\n */\n\nimport { getValidToken } from \"./oauth.ts\";\n\n// Helper for Cross-Platform environment access\nfunction getEnv(key: string): string | undefined {\n // @ts-ignore - Deno global\n if (typeof Deno !== \"undefined\") return Deno.env.get(key);\n // @ts-ignore - process global\n if (typeof process !== \"undefined\" && process.env) return process.env[key];\n return undefined;\n}\n\nconst DOCS_API_BASE = \"https://docs.googleapis.com/v1\";\nconst DRIVE_API_BASE = \"https://www.googleapis.com/drive/v3\";\n\nexport interface Document {\n documentId: string;\n title: string;\n body: {\n content: StructuralElement[];\n };\n revisionId: string;\n suggestionsViewMode: string;\n documentStyle: DocumentStyle;\n}\n\nexport interface StructuralElement {\n startIndex: number;\n endIndex: number;\n paragraph?: Paragraph;\n table?: Table;\n sectionBreak?: SectionBreak;\n}\n\nexport interface Paragraph {\n elements: ParagraphElement[];\n paragraphStyle?: ParagraphStyle;\n bullet?: Bullet;\n}\n\nexport interface ParagraphElement {\n startIndex: number;\n endIndex: number;\n textRun?: TextRun;\n inlineObjectElement?: InlineObjectElement;\n}\n\nexport interface TextRun {\n content: string;\n textStyle?: TextStyle;\n}\n\nexport interface TextStyle {\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strikethrough?: boolean;\n fontSize?: Dimension;\n foregroundColor?: Color;\n backgroundColor?: Color;\n fontFamily?: string;\n link?: Link;\n}\n\nexport interface Link {\n url?: string;\n bookmarkId?: string;\n headingId?: string;\n}\n\nexport interface Dimension {\n magnitude: number;\n unit: string;\n}\n\nexport interface Color {\n rgbColor?: RgbColor;\n}\n\nexport interface RgbColor {\n red: number;\n green: number;\n blue: number;\n}\n\nexport interface ParagraphStyle {\n headingId?: string;\n namedStyleType?: string;\n alignment?: string;\n lineSpacing?: number;\n direction?: string;\n spacingMode?: string;\n spaceAbove?: Dimension;\n spaceBelow?: Dimension;\n indentFirstLine?: Dimension;\n indentStart?: Dimension;\n indentEnd?: Dimension;\n}\n\nexport interface Bullet {\n listId: string;\n nestingLevel?: number;\n textStyle?: TextStyle;\n}\n\nexport interface Table {\n rows: number;\n columns: number;\n tableRows: TableRow[];\n tableStyle?: TableStyle;\n}\n\nexport interface TableRow {\n startIndex: number;\n endIndex: number;\n tableCells: TableCell[];\n}\n\nexport interface TableCell {\n startIndex: number;\n endIndex: number;\n content: StructuralElement[];\n tableCellStyle?: TableCellStyle;\n}\n\nexport interface TableCellStyle {\n rowSpan?: number;\n columnSpan?: number;\n backgroundColor?: Color;\n borderLeft?: TableCellBorder;\n borderRight?: TableCellBorder;\n borderTop?: TableCellBorder;\n borderBottom?: TableCellBorder;\n paddingLeft?: Dimension;\n paddingRight?: Dimension;\n paddingTop?: Dimension;\n paddingBottom?: Dimension;\n}\n\nexport interface TableCellBorder {\n color?: Color;\n width?: Dimension;\n dashStyle?: string;\n}\n\nexport interface TableStyle {\n tableColumnProperties?: TableColumnProperties[];\n}\n\nexport interface TableColumnProperties {\n width?: Dimension;\n widthType?: string;\n}\n\nexport interface SectionBreak {\n sectionStyle?: SectionStyle;\n}\n\nexport interface SectionStyle {\n columnSeparatorStyle?: string;\n contentDirection?: string;\n marginTop?: Dimension;\n marginBottom?: Dimension;\n marginRight?: Dimension;\n marginLeft?: Dimension;\n pageNumberStart?: number;\n}\n\nexport interface DocumentStyle {\n background?: Background;\n pageNumberStart?: number;\n marginTop?: Dimension;\n marginBottom?: Dimension;\n marginRight?: Dimension;\n marginLeft?: Dimension;\n pageSize?: Size;\n marginHeader?: Dimension;\n marginFooter?: Dimension;\n useFirstPageHeaderFooter?: boolean;\n}\n\nexport interface Background {\n color?: Color;\n}\n\nexport interface Size {\n height?: Dimension;\n width?: Dimension;\n}\n\nexport interface InlineObjectElement {\n inlineObjectId: string;\n textStyle?: TextStyle;\n}\n\nexport interface DocumentFile {\n id: string;\n name: string;\n mimeType: string;\n createdTime: string;\n modifiedTime: string;\n webViewLink: string;\n iconLink?: string;\n thumbnailLink?: string;\n}\n\nexport interface CreateDocumentOptions {\n title: string;\n}\n\nexport interface BatchUpdateRequest {\n requests: Request[];\n}\n\nexport interface Request {\n insertText?: InsertTextRequest;\n deleteContentRange?: DeleteContentRangeRequest;\n replaceAllText?: ReplaceAllTextRequest;\n updateTextStyle?: UpdateTextStyleRequest;\n updateParagraphStyle?: UpdateParagraphStyleRequest;\n insertPageBreak?: InsertPageBreakRequest;\n insertTable?: InsertTableRequest;\n deleteTableRow?: DeleteTableRowRequest;\n deleteTableColumn?: DeleteTableColumnRequest;\n createParagraphBullets?: CreateParagraphBulletsRequest;\n deleteParagraphBullets?: DeleteParagraphBulletsRequest;\n}\n\nexport interface InsertTextRequest {\n text: string;\n location: Location;\n}\n\nexport interface DeleteContentRangeRequest {\n range: Range;\n}\n\nexport interface ReplaceAllTextRequest {\n containsText: ContainsText;\n replaceText: string;\n}\n\nexport interface UpdateTextStyleRequest {\n range: Range;\n textStyle: TextStyle;\n fields: string;\n}\n\nexport interface UpdateParagraphStyleRequest {\n range: Range;\n paragraphStyle: ParagraphStyle;\n fields: string;\n}\n\nexport interface InsertPageBreakRequest {\n location: Location;\n}\n\nexport interface InsertTableRequest {\n rows: number;\n columns: number;\n location: Location;\n}\n\nexport interface DeleteTableRowRequest {\n tableCellLocation: TableCellLocation;\n}\n\nexport interface DeleteTableColumnRequest {\n tableCellLocation: TableCellLocation;\n}\n\nexport interface CreateParagraphBulletsRequest {\n range: Range;\n bulletPreset: string;\n}\n\nexport interface DeleteParagraphBulletsRequest {\n range: Range;\n}\n\nexport interface Location {\n index: number;\n segmentId?: string;\n}\n\nexport interface Range {\n startIndex: number;\n endIndex: number;\n segmentId?: string;\n}\n\nexport interface ContainsText {\n text: string;\n matchCase: boolean;\n}\n\nexport interface TableCellLocation {\n tableStartLocation: Location;\n rowIndex: number;\n columnIndex: number;\n}\n\nexport interface BatchUpdateResponse {\n documentId: string;\n replies: Reply[];\n writeControl?: WriteControl;\n}\n\nexport interface Reply {\n [key: string]: unknown;\n}\n\nexport interface WriteControl {\n requiredRevisionId: string;\n targetRevisionId: string;\n}\n\n/**\n * Google Docs OAuth provider configuration\n */\nexport const docsOAuthProvider = {\n name: \"docs-google\",\n authorizationUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n tokenUrl: \"https://oauth2.googleapis.com/token\",\n clientId: getEnv(\"GOOGLE_CLIENT_ID\") || \"\",\n clientSecret: getEnv(\"GOOGLE_CLIENT_SECRET\") || \"\",\n scopes: [\n \"https://www.googleapis.com/auth/documents.readonly\",\n \"https://www.googleapis.com/auth/documents\",\n \"https://www.googleapis.com/auth/drive.readonly\",\n ],\n callbackPath: \"/api/auth/docs-google/callback\",\n};\n\n/**\n * Create a Docs client for a specific user\n */\nexport function createDocsClient(userId: string): {\n listDocuments(options?: {\n maxResults?: number;\n orderBy?: \"createdTime\" | \"modifiedTime\" | \"name\";\n }): Promise<DocumentFile[]>;\n getDocument(documentId: string): Promise<Document>;\n createDocument(options: CreateDocumentOptions): Promise<Document>;\n updateDocument(documentId: string, requests: Request[]): Promise<BatchUpdateResponse>;\n insertText(documentId: string, text: string, index: number): Promise<BatchUpdateResponse>;\n deleteContent(documentId: string, startIndex: number, endIndex: number): Promise<BatchUpdateResponse>;\n replaceAllText(\n documentId: string,\n searchText: string,\n replaceText: string,\n matchCase?: boolean,\n ): Promise<BatchUpdateResponse>;\n searchDocuments(query: string, maxResults?: number): Promise<DocumentFile[]>;\n extractText(document: Document): string;\n createDocumentWithContent(title: string, content: string): Promise<Document>;\n} {\n async function getAccessToken(): Promise<string> {\n const token = await getValidToken(docsOAuthProvider, userId, \"docs-google\");\n if (!token) {\n throw new Error(\"Google Docs not connected. Please connect your Google account first.\");\n }\n return token;\n }\n\n async function apiRequest<T>(\n baseUrl: string,\n label: string,\n endpoint: string,\n options: RequestInit = {},\n ): Promise<T> {\n const accessToken = await getAccessToken();\n\n const response = await fetch(`${baseUrl}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`${label} API error: ${response.status} - ${error}`);\n }\n\n return response.json();\n }\n\n function docsApiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n return apiRequest<T>(DOCS_API_BASE, \"Docs\", endpoint, options);\n }\n\n function driveApiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n return apiRequest<T>(DRIVE_API_BASE, \"Drive\", endpoint, options);\n }\n\n return {\n /**\n * List documents from Google Drive\n */\n async listDocuments(options = {}): Promise<DocumentFile[]> {\n const params = new URLSearchParams({\n q: \"mimeType='application/vnd.google-apps.document' and trashed=false\",\n fields: \"files(id,name,mimeType,createdTime,modifiedTime,webViewLink,iconLink,thumbnailLink)\",\n pageSize: String(options.maxResults || 20),\n orderBy: `${options.orderBy || \"modifiedTime\"} desc`,\n });\n\n const result = await driveApiRequest<{ files: DocumentFile[] }>(`/files?${params.toString()}`);\n return result.files || [];\n },\n\n /**\n * Get document content and metadata\n */\n getDocument(documentId: string): Promise<Document> {\n return docsApiRequest<Document>(`/documents/${documentId}`);\n },\n\n /**\n * Create a new document\n */\n createDocument(options: CreateDocumentOptions): Promise<Document> {\n return docsApiRequest<Document>(\"/documents\", {\n method: \"POST\",\n body: JSON.stringify({ title: options.title }),\n });\n },\n\n /**\n * Update document using batch requests\n */\n updateDocument(documentId: string, requests: Request[]): Promise<BatchUpdateResponse> {\n return docsApiRequest<BatchUpdateResponse>(`/documents/${documentId}:batchUpdate`, {\n method: \"POST\",\n body: JSON.stringify({ requests }),\n });\n },\n\n /**\n * Insert text at a specific location\n */\n insertText(documentId: string, text: string, index: number): Promise<BatchUpdateResponse> {\n return this.updateDocument(documentId, [\n {\n insertText: {\n text,\n location: { index },\n },\n },\n ]);\n },\n\n /**\n * Delete content in a range\n */\n deleteContent(documentId: string, startIndex: number, endIndex: number): Promise<BatchUpdateResponse> {\n return this.updateDocument(documentId, [\n {\n deleteContentRange: {\n range: { startIndex, endIndex },\n },\n },\n ]);\n },\n\n /**\n * Replace all occurrences of text\n */\n replaceAllText(\n documentId: string,\n searchText: string,\n replaceText: string,\n matchCase = false,\n ): Promise<BatchUpdateResponse> {\n return this.updateDocument(documentId, [\n {\n replaceAllText: {\n containsText: {\n text: searchText,\n matchCase,\n },\n replaceText,\n },\n },\n ]);\n },\n\n /**\n * Search documents by query\n */\n async searchDocuments(query: string, maxResults = 20): Promise<DocumentFile[]> {\n const params = new URLSearchParams({\n q: `mimeType='application/vnd.google-apps.document' and trashed=false and fullText contains '${query}'`,\n fields: \"files(id,name,mimeType,createdTime,modifiedTime,webViewLink,iconLink,thumbnailLink)\",\n pageSize: String(maxResults),\n orderBy: \"modifiedTime desc\",\n });\n\n const result = await driveApiRequest<{ files: DocumentFile[] }>(`/files?${params.toString()}`);\n return result.files || [];\n },\n\n /**\n * Extract plain text from document body\n */\n extractText(document: Document): string {\n const textParts: string[] = [];\n\n function processElement(element: StructuralElement): void {\n if (element.paragraph) {\n for (const el of element.paragraph.elements) {\n if (el.textRun) textParts.push(el.textRun.content);\n }\n return;\n }\n\n if (element.table) {\n for (const row of element.table.tableRows) {\n for (const cell of row.tableCells) {\n for (const child of cell.content) processElement(child);\n }\n }\n }\n }\n\n for (const element of document.body.content) processElement(element);\n return textParts.join(\"\");\n },\n\n /**\n * Create a document with initial content\n */\n async createDocumentWithContent(title: string, content: string): Promise<Document> {\n const doc = await this.createDocument({ title });\n await this.insertText(doc.documentId, content, 1);\n return this.getDocument(doc.documentId);\n },\n };\n}\n\nexport type DocsClient = ReturnType<typeof createDocsClient>;\n",
|
|
278
|
+
"lib/oauth.ts": "import { type OAuthToken, tokenStore } from \"./token-store.ts\";\n\nexport interface OAuthProvider {\n name: string;\n authorizationUrl: string;\n tokenUrl: string;\n clientId: string;\n clientSecret: string;\n scopes: string[];\n callbackPath: string;\n}\n\nfunction getExpiresAt(expiresIn: unknown): number | undefined {\n if (typeof expiresIn !== \"number\") return undefined;\n return Date.now() + expiresIn * 1000;\n}\n\nasync function postForm(url: string, body: Record<string, string>): Promise<any> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams(body),\n });\n\n if (response.ok) return response.json();\n\n const error = await response.text();\n throw new Error(`Token request failed: ${response.status} - ${error}`);\n}\n\nexport function getAuthorizationUrl(\n provider: OAuthProvider,\n state: string,\n redirectUri: string,\n): string {\n const params = new URLSearchParams({\n client_id: provider.clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: provider.scopes.join(\" \"),\n state,\n access_type: \"offline\",\n prompt: \"consent\",\n });\n\n return `${provider.authorizationUrl}?${params.toString()}`;\n}\n\nexport async function exchangeCodeForTokens(\n provider: OAuthProvider,\n code: string,\n redirectUri: string,\n): Promise<OAuthToken> {\n const data = await postForm(provider.tokenUrl, {\n client_id: provider.clientId,\n client_secret: provider.clientSecret,\n code,\n grant_type: \"authorization_code\",\n redirect_uri: redirectUri,\n });\n\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: getExpiresAt(data.expires_in),\n tokenType: data.token_type ?? \"Bearer\",\n scope: data.scope,\n };\n}\n\nexport async function refreshAccessToken(\n provider: OAuthProvider,\n refreshToken: string,\n): Promise<OAuthToken> {\n const data = await postForm(provider.tokenUrl, {\n client_id: provider.clientId,\n client_secret: provider.clientSecret,\n refresh_token: refreshToken,\n grant_type: \"refresh_token\",\n });\n\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n expiresAt: getExpiresAt(data.expires_in),\n tokenType: data.token_type ?? \"Bearer\",\n scope: data.scope,\n };\n}\n\nexport async function getValidToken(\n provider: OAuthProvider,\n userId: string,\n service: string,\n): Promise<string | null> {\n const token = await tokenStore.getToken(userId, service);\n if (!token) return null;\n\n const isExpired = token.expiresAt\n ? token.expiresAt < Date.now() + 5 * 60 * 1000\n : false;\n\n if (!isExpired || !token.refreshToken) return token.accessToken;\n\n try {\n const newToken = await refreshAccessToken(provider, token.refreshToken);\n await tokenStore.setToken(userId, service, newToken);\n return newToken.accessToken;\n } catch {\n // Refresh failed, user needs to re-authorize\n await tokenStore.revokeToken(userId, service);\n return null;\n }\n}\n",
|
|
279
|
+
"tools/update-document.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createDocsClient, type Request } from \"../../lib/docs-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"update-document\",\n description:\n \"Update a Google Docs document using batch requests. Supports inserting text, deleting content, replacing text, and more.\",\n inputSchema: z\n .object({\n documentId: z.string().describe(\"The ID of the document to update\"),\n requests: z\n .array(z.any())\n .describe(\n \"Array of batch update requests. See Google Docs API documentation for request types: insertText, deleteContentRange, replaceAllText, etc.\",\n ),\n })\n .or(\n z.object({\n documentId: z.string().describe(\"The ID of the document to update\"),\n operation: z\n .object({\n type: z\n .enum([\"insertText\", \"deleteContent\", \"replaceAllText\"])\n .describe(\"Type of operation to perform\"),\n insertText: z\n .object({\n text: z.string().describe(\"Text to insert\"),\n index: z.number().describe(\"Position to insert at (1 = start of document)\"),\n })\n .optional()\n .describe(\"Parameters for insertText operation\"),\n deleteContent: z\n .object({\n startIndex: z.number().describe(\"Start position of content to delete\"),\n endIndex: z.number().describe(\"End position of content to delete\"),\n })\n .optional()\n .describe(\"Parameters for deleteContent operation\"),\n replaceAllText: z\n .object({\n searchText: z.string().describe(\"Text to search for\"),\n replaceText: z.string().describe(\"Text to replace with\"),\n matchCase: z.boolean().default(false).describe(\"Whether to match case\"),\n })\n .optional()\n .describe(\"Parameters for replaceAllText operation\"),\n })\n .describe(\"Simple operation to perform\"),\n }),\n ),\n async execute(input): Promise<{\n documentId: string;\n success: true;\n replies: unknown;\n writeControl?: unknown;\n }> {\n const client = createDocsClient(DEFAULT_USER_ID);\n\n if (\"operation\" in input) {\n const { documentId, operation } = input;\n\n if (operation.type === \"insertText\") {\n const params = operation.insertText;\n if (!params) throw new Error(\"insertText parameters required\");\n\n const result = await client.insertText(documentId, params.text, params.index);\n return { documentId: result.documentId, success: true, replies: result.replies };\n }\n\n if (operation.type === \"deleteContent\") {\n const params = operation.deleteContent;\n if (!params) throw new Error(\"deleteContent parameters required\");\n\n const result = await client.deleteContent(documentId, params.startIndex, params.endIndex);\n return { documentId: result.documentId, success: true, replies: result.replies };\n }\n\n if (operation.type === \"replaceAllText\") {\n const params = operation.replaceAllText;\n if (!params) throw new Error(\"replaceAllText parameters required\");\n\n const result = await client.replaceAllText(\n documentId,\n params.searchText,\n params.replaceText,\n params.matchCase,\n );\n return { documentId: result.documentId, success: true, replies: result.replies };\n }\n\n throw new Error(`Unknown operation type: ${operation.type}`);\n }\n\n const { documentId, requests } = input;\n const result = await client.updateDocument(documentId, requests as Request[]);\n\n return {\n documentId: result.documentId,\n success: true,\n replies: result.replies,\n writeControl: result.writeControl,\n };\n },\n});\n",
|
|
280
|
+
"tools/search-documents.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createDocsClient } from \"../../lib/docs-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"search-documents\",\n description:\n \"Search for Google Docs documents by query string. Searches document names and content. Returns matching document IDs, names, and metadata.\",\n inputSchema: z.object({\n query: z\n .string()\n .describe(\"Search query to find documents. Searches in document names and content.\"),\n maxResults: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of results to return\"),\n }),\n async execute({ query, maxResults }) {\n const client = createDocsClient(DEFAULT_USER_ID);\n const documents = await client.searchDocuments(query, maxResults);\n\n return documents.map(({ id, name, webViewLink, createdTime, modifiedTime, thumbnailLink }) => ({\n id,\n name,\n url: webViewLink,\n createdTime,\n modifiedTime,\n thumbnail: thumbnailLink,\n }));\n },\n});\n",
|
|
281
|
+
"tools/list-documents.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createDocsClient } from \"../../lib/docs-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"list-documents\",\n description:\n \"List recent Google Docs documents from Google Drive. Returns document names, IDs, and metadata.\",\n inputSchema: z.object({\n maxResults: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of documents to return\"),\n orderBy: z\n .enum([\"createdTime\", \"modifiedTime\", \"name\"])\n .default(\"modifiedTime\")\n .describe(\"Sort order for results\"),\n }),\n async execute({ maxResults, orderBy }) {\n const client = createDocsClient(DEFAULT_USER_ID);\n const documents = await client.listDocuments({ maxResults, orderBy });\n\n return documents.map(\n ({\n id,\n name,\n webViewLink: url,\n createdTime,\n modifiedTime,\n thumbnailLink: thumbnail,\n }) => ({\n id,\n name,\n url,\n createdTime,\n modifiedTime,\n thumbnail,\n }),\n );\n },\n});\n",
|
|
282
|
+
"tools/create-document.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createDocsClient } from \"../../lib/docs-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"create-document\",\n description:\n \"Create a new Google Docs document with optional initial content. Returns the new document ID and URL.\",\n inputSchema: z.object({\n title: z.string().describe(\"Title of the new document\"),\n content: z\n .string()\n .optional()\n .describe(\"Optional initial text content to insert into the document\"),\n }),\n async execute({ title, content }) {\n const client = createDocsClient(DEFAULT_USER_ID);\n\n const document = content\n ? await client.createDocumentWithContent(title, content)\n : await client.createDocument({ title });\n\n const documents = await client.listDocuments({ maxResults: 1 });\n const webViewLink = documents.find((d) => d.id === document.documentId)?.webViewLink;\n\n return {\n documentId: document.documentId,\n title: document.title,\n url: webViewLink ?? `https://docs.google.com/document/d/${document.documentId}/edit`,\n revisionId: document.revisionId,\n };\n },\n});\n",
|
|
283
|
+
"tools/get-document.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createDocsClient } from \"../../lib/docs-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"get-document\",\n description:\n \"Get a Google Docs document's content and metadata. Returns the full document structure including text, formatting, and styles.\",\n inputSchema: z.object({\n documentId: z.string().describe(\"The ID of the document to retrieve\"),\n extractTextOnly: z\n .boolean()\n .default(false)\n .describe(\"If true, only return plain text content without formatting\"),\n }),\n async execute({ documentId, extractTextOnly }) {\n const client = createDocsClient(DEFAULT_USER_ID);\n const document = await client.getDocument(documentId);\n\n const { documentId: id, title, revisionId } = document;\n\n if (extractTextOnly) {\n return {\n documentId: id,\n title,\n text: client.extractText(document),\n revisionId,\n };\n }\n\n return {\n documentId: id,\n title,\n revisionId,\n body: document.body,\n documentStyle: document.documentStyle,\n };\n },\n});\n",
|
|
284
|
+
"app/api/auth/docs-google/route.ts": "import { createOAuthInitHandler, docsGoogleConfig, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(docsGoogleConfig, { tokenStore: memoryTokenStore });\n",
|
|
285
|
+
"app/api/auth/docs-google/callback/route.ts": "import { createOAuthCallbackHandler, docsGoogleConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(docsGoogleConfig, { tokenStore: hybridTokenStore });\n",
|
|
286
|
+
".env.example": "# Google Docs Integration\n# Create OAuth credentials at https://console.cloud.google.com/apis/credentials\n# Make sure to enable:\n# - Google Docs API: https://console.cloud.google.com/apis/library/docs.googleapis.com\n# - Google Drive API: https://console.cloud.google.com/apis/library/drive.googleapis.com\n\nGOOGLE_CLIENT_ID=your_client_id_here\nGOOGLE_CLIENT_SECRET=your_client_secret_here\n"
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
"integration:pipedrive": {
|
|
290
|
+
"files": {
|
|
291
|
+
"lib/pipedrive-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst PIPEDRIVE_BASE_URL = \"https://api.pipedrive.com/v1\";\n\ninterface PipedriveResponse<T> {\n success: boolean;\n data: T;\n additional_data?: {\n pagination?: {\n start: number;\n limit: number;\n more_items_in_collection: boolean;\n next_start?: number;\n };\n };\n}\n\ninterface PipedriveDeal {\n id: number;\n title: string;\n value: number;\n currency: string;\n status: string;\n stage_id: number;\n person_id: number | null;\n person_name: string | null;\n org_id: number | null;\n org_name: string | null;\n owner_name: string;\n expected_close_date: string | null;\n add_time: string;\n update_time: string;\n won_time: string | null;\n lost_time: string | null;\n close_time: string | null;\n}\n\ninterface PipedrivePerson {\n id: number;\n name: string;\n first_name: string;\n last_name: string;\n email: Array<{ value: string; primary: boolean }>;\n phone: Array<{ value: string; primary: boolean }>;\n org_id: number | null;\n org_name: string | null;\n owner_id: number;\n owner_name: string;\n add_time: string;\n update_time: string;\n}\n\ninterface PipedriveStage {\n id: number;\n name: string;\n order_nr: number;\n pipeline_id: number;\n pipeline_name: string;\n}\n\nasync function pipedriveFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Pipedrive. Please connect your account.\");\n }\n\n const url = new URL(`${PIPEDRIVE_BASE_URL}${endpoint}`);\n url.searchParams.set(\"api_token\", token);\n\n const response = await fetch(url.toString(), {\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as { error?: string }));\n throw new Error(`Pipedrive API error: ${response.status} ${error.error ?? response.statusText}`);\n }\n\n return response.json();\n}\n\nfunction buildEndpoint(path: string, params: URLSearchParams): string {\n const queryString = params.toString();\n return queryString ? `${path}?${queryString}` : path;\n}\n\nexport async function listDeals(options?: {\n status?: \"open\" | \"won\" | \"lost\" | \"all\";\n ownerId?: number;\n stageId?: number;\n limit?: number;\n}): Promise<PipedriveDeal[]> {\n const params = new URLSearchParams();\n\n if (options?.status) params.set(\"status\", options.status);\n if (options?.ownerId) params.set(\"user_id\", options.ownerId.toString());\n if (options?.stageId) params.set(\"stage_id\", options.stageId.toString());\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n\n const endpoint = buildEndpoint(\"/deals\", params);\n const response = await pipedriveFetch<PipedriveResponse<PipedriveDeal[]>>(endpoint);\n\n return response.data || [];\n}\n\nexport async function getDeal(dealId: number): Promise<PipedriveDeal> {\n const response = await pipedriveFetch<PipedriveResponse<PipedriveDeal>>(`/deals/${dealId}`);\n return response.data;\n}\n\nexport async function createDeal(options: {\n title: string;\n value?: number;\n currency?: string;\n personId?: number;\n orgId?: number;\n stageId?: number;\n expectedCloseDate?: string;\n}): Promise<PipedriveDeal> {\n const body: Record<string, unknown> = { title: options.title };\n\n if (options.value !== undefined) body.value = options.value;\n if (options.currency) body.currency = options.currency;\n if (options.personId) body.person_id = options.personId;\n if (options.orgId) body.org_id = options.orgId;\n if (options.stageId) body.stage_id = options.stageId;\n if (options.expectedCloseDate) body.expected_close_date = options.expectedCloseDate;\n\n const response = await pipedriveFetch<PipedriveResponse<PipedriveDeal>>(\"/deals\", {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n\n return response.data;\n}\n\nexport async function updateDeal(\n dealId: number,\n updates: {\n title?: string;\n value?: number;\n status?: string;\n stageId?: number;\n personId?: number;\n orgId?: number;\n expectedCloseDate?: string;\n },\n): Promise<PipedriveDeal> {\n const body: Record<string, unknown> = {};\n\n if (updates.title !== undefined) body.title = updates.title;\n if (updates.value !== undefined) body.value = updates.value;\n if (updates.status !== undefined) body.status = updates.status;\n if (updates.stageId !== undefined) body.stage_id = updates.stageId;\n if (updates.personId !== undefined) body.person_id = updates.personId;\n if (updates.orgId !== undefined) body.org_id = updates.orgId;\n if (updates.expectedCloseDate !== undefined) body.expected_close_date = updates.expectedCloseDate;\n\n const response = await pipedriveFetch<PipedriveResponse<PipedriveDeal>>(`/deals/${dealId}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n });\n\n return response.data;\n}\n\nexport async function listPersons(options?: {\n searchTerm?: string;\n limit?: number;\n}): Promise<PipedrivePerson[]> {\n const params = new URLSearchParams();\n\n if (options?.searchTerm) params.set(\"term\", options.searchTerm);\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n\n const endpoint = buildEndpoint(\"/persons\", params);\n const response = await pipedriveFetch<PipedriveResponse<PipedrivePerson[]>>(endpoint);\n\n return response.data || [];\n}\n\nexport async function getPerson(personId: number): Promise<PipedrivePerson> {\n const response = await pipedriveFetch<PipedriveResponse<PipedrivePerson>>(`/persons/${personId}`);\n return response.data;\n}\n\nexport async function createPerson(options: {\n name: string;\n email?: string;\n phone?: string;\n orgId?: number;\n}): Promise<PipedrivePerson> {\n const body: Record<string, unknown> = { name: options.name };\n\n if (options.email) body.email = [{ value: options.email, primary: true }];\n if (options.phone) body.phone = [{ value: options.phone, primary: true }];\n if (options.orgId) body.org_id = options.orgId;\n\n const response = await pipedriveFetch<PipedriveResponse<PipedrivePerson>>(\"/persons\", {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n\n return response.data;\n}\n\nexport async function listStages(): Promise<PipedriveStage[]> {\n const response = await pipedriveFetch<PipedriveResponse<PipedriveStage[]>>(\"/stages\");\n return response.data || [];\n}\n\nexport async function getCurrentUser(): Promise<{ id: number; name: string; email: string }> {\n const response = await pipedriveFetch<\n PipedriveResponse<{ id: number; name: string; email: string }>\n >(\"/users/me\");\n return response.data;\n}\n",
|
|
292
|
+
"tools/update-deal.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { updateDeal } from \"../../lib/pipedrive-client.ts\";\n\nexport default tool({\n id: \"update-deal\",\n description: \"Update an existing deal in Pipedrive with new information.\",\n inputSchema: z.object({\n dealId: z.number().describe(\"The ID of the deal to update\"),\n title: z.string().optional().describe(\"New title/name for the deal\"),\n value: z.number().optional().describe(\"New monetary value for the deal\"),\n status: z.string().optional().describe(\"New status (e.g., open, won, lost)\"),\n stageId: z.number().optional().describe(\"New pipeline stage ID\"),\n personId: z.number().optional().describe(\"New person/contact ID\"),\n orgId: z.number().optional().describe(\"New organization ID\"),\n expectedCloseDate: z\n .string()\n .optional()\n .describe(\"New expected close date in YYYY-MM-DD format\"),\n }),\n async execute({\n dealId,\n title,\n value,\n status,\n stageId,\n personId,\n orgId,\n expectedCloseDate,\n }) {\n const deal = await updateDeal(dealId, {\n title,\n value,\n status,\n stageId,\n personId,\n orgId,\n expectedCloseDate,\n });\n\n return {\n success: true,\n deal: {\n id: deal.id,\n title: deal.title,\n value: deal.value,\n status: deal.status,\n stageId: deal.stage_id,\n personName: deal.person_name,\n orgName: deal.org_name,\n expectedCloseDate: deal.expected_close_date,\n updateTime: deal.update_time,\n },\n };\n },\n});\n",
|
|
293
|
+
"tools/list-persons.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listPersons } from \"../../lib/pipedrive-client.ts\";\n\nexport default tool({\n id: \"list-persons\",\n description:\n \"List contacts/persons from Pipedrive. Can optionally search by name or email.\",\n inputSchema: z.object({\n searchTerm: z\n .string()\n .optional()\n .describe(\"Search term to filter persons by name or email\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of persons to return\"),\n }),\n async execute({ searchTerm, limit }) {\n const persons = await listPersons({ searchTerm, limit });\n\n return persons.map((person) => ({\n id: person.id,\n name: person.name,\n firstName: person.first_name,\n lastName: person.last_name,\n email: person.email?.[0]?.value ?? null,\n phone: person.phone?.[0]?.value ?? null,\n orgId: person.org_id,\n orgName: person.org_name,\n ownerName: person.owner_name,\n addTime: person.add_time,\n updateTime: person.update_time,\n }));\n },\n});\n",
|
|
294
|
+
"tools/list-deals.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listDeals } from \"../../lib/pipedrive-client.ts\";\n\nexport default tool({\n id: \"list-deals\",\n description:\n \"List deals from Pipedrive. Can filter by status, owner, or stage to get specific deals in the sales pipeline.\",\n inputSchema: z.object({\n status: z\n .enum([\"open\", \"won\", \"lost\", \"all\"])\n .default(\"open\")\n .describe(\"Filter deals by status\"),\n ownerId: z.number().optional().describe(\"Filter deals by owner user ID\"),\n stageId: z.number().optional().describe(\"Filter deals by stage ID\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of deals to return\"),\n }),\n async execute({ status, ownerId, stageId, limit }) {\n const deals = await listDeals({ status, ownerId, stageId, limit });\n\n return deals.map(\n ({\n id,\n title,\n value,\n currency,\n status,\n stage_id,\n person_name,\n org_name,\n owner_name,\n expected_close_date,\n add_time,\n update_time,\n }) => ({\n id,\n title,\n value,\n currency,\n status,\n stageId: stage_id,\n personName: person_name,\n orgName: org_name,\n ownerName: owner_name,\n expectedCloseDate: expected_close_date,\n addTime: add_time,\n updateTime: update_time,\n }),\n );\n },\n});\n",
|
|
295
|
+
"tools/create-deal.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createDeal } from \"../../lib/pipedrive-client.ts\";\n\nexport default tool({\n id: \"create-deal\",\n description: \"Create a new deal in the Pipedrive sales pipeline.\",\n inputSchema: z.object({\n title: z.string().describe(\"The title/name of the deal\"),\n value: z.number().optional().describe(\"The monetary value of the deal\"),\n currency: z\n .string()\n .default(\"USD\")\n .describe(\"Currency code (e.g., USD, EUR, GBP)\"),\n personId: z\n .number()\n .optional()\n .describe(\"ID of the person/contact associated with the deal\"),\n orgId: z\n .number()\n .optional()\n .describe(\"ID of the organization associated with the deal\"),\n stageId: z.number().optional().describe(\"ID of the pipeline stage for the deal\"),\n expectedCloseDate: z\n .string()\n .optional()\n .describe(\"Expected close date in YYYY-MM-DD format\"),\n }),\n async execute(input) {\n const deal = await createDeal(input);\n\n return {\n success: true,\n deal: {\n id: deal.id,\n title: deal.title,\n value: deal.value,\n currency: deal.currency,\n stageId: deal.stage_id,\n personName: deal.person_name,\n orgName: deal.org_name,\n expectedCloseDate: deal.expected_close_date,\n },\n };\n },\n});\n",
|
|
296
|
+
"tools/get-deal.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getDeal } from \"../../lib/pipedrive-client.ts\";\n\nexport default tool({\n id: \"get-deal\",\n description: \"Get detailed information about a specific deal in Pipedrive by its ID.\",\n inputSchema: z.object({\n dealId: z.number().describe(\"The ID of the deal to retrieve\"),\n }),\n async execute({ dealId }) {\n const deal = await getDeal(dealId);\n\n return {\n id: deal.id,\n title: deal.title,\n value: deal.value,\n currency: deal.currency,\n status: deal.status,\n stageId: deal.stage_id,\n personId: deal.person_id,\n personName: deal.person_name,\n orgId: deal.org_id,\n orgName: deal.org_name,\n ownerName: deal.owner_name,\n expectedCloseDate: deal.expected_close_date,\n addTime: deal.add_time,\n updateTime: deal.update_time,\n wonTime: deal.won_time,\n lostTime: deal.lost_time,\n closeTime: deal.close_time,\n };\n },\n});\n",
|
|
297
|
+
"app/api/auth/pipedrive/route.ts": "import { createOAuthInitHandler, pipedriveConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(pipedriveConfig);\n",
|
|
298
|
+
"app/api/auth/pipedrive/callback/route.ts": "import { createOAuthCallbackHandler, memoryTokenStore, pipedriveConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(pipedriveConfig, { tokenStore: hybridTokenStore });\n",
|
|
299
|
+
".env.example": "# Pipedrive OAuth Configuration\n# Get your credentials from https://developers.pipedrive.com/docs/marketplace\nPIPEDRIVE_CLIENT_ID=your-client-id\nPIPEDRIVE_CLIENT_SECRET=your-client-secret\n"
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
"integration:sentry": {
|
|
303
|
+
"files": {
|
|
304
|
+
"lib/sentry-client.ts": "import { getApiKey, getOrg } from \"./token-store.ts\";\n\nconst SENTRY_API_BASE_URL = \"https://sentry.io/api/0\";\n\nexport interface Organization {\n id: string;\n slug: string;\n name: string;\n dateCreated: string;\n status: {\n id: string;\n name: string;\n };\n avatar?: {\n avatarType: string;\n avatarUuid: string | null;\n };\n features: string[];\n}\n\nexport interface Project {\n id: string;\n slug: string;\n name: string;\n platform?: string;\n dateCreated: string;\n isBookmarked: boolean;\n isMember: boolean;\n features: string[];\n firstEvent: string | null;\n firstTransactionEvent: boolean;\n access: string[];\n hasAccess: boolean;\n hasCustomMetrics: boolean;\n hasMinifiedStackTrace: boolean;\n hasMonitors: boolean;\n hasProfiles: boolean;\n hasReplays: boolean;\n hasSessions: boolean;\n team?: {\n id: string;\n name: string;\n slug: string;\n };\n teams: Array<{\n id: string;\n name: string;\n slug: string;\n }>;\n eventProcessing: {\n symbolicationDegraded: boolean;\n };\n status: string;\n}\n\nexport interface Issue {\n id: string;\n shareId: string | null;\n shortId: string;\n title: string;\n culprit: string;\n permalink: string;\n logger: string | null;\n level: string;\n status: string;\n statusDetails: Record<string, unknown>;\n substatus: string | null;\n isPublic: boolean;\n platform: string;\n project: {\n id: string;\n name: string;\n slug: string;\n platform: string;\n };\n type: string;\n metadata: {\n value?: string;\n type?: string;\n filename?: string;\n function?: string;\n title?: string;\n };\n numComments: number;\n assignedTo: {\n id: string;\n name: string;\n type: string;\n } | null;\n isBookmarked: boolean;\n isSubscribed: boolean;\n subscriptionDetails: {\n reason?: string;\n } | null;\n hasSeen: boolean;\n annotations: string[];\n isUnhandled: boolean;\n count: string;\n userCount: number;\n firstSeen: string;\n lastSeen: string;\n stats?: {\n \"24h\": Array<[number, number]>;\n };\n}\n\nexport interface Event {\n id: string;\n groupID: string;\n eventID: string;\n projectID: string;\n size: number;\n platform: string;\n message: string;\n dateCreated: string;\n dateReceived: string;\n user: {\n id?: string;\n email?: string;\n username?: string;\n ip_address?: string;\n } | null;\n entries: Array<{\n type: string;\n data: unknown;\n }>;\n contexts: Record<string, unknown>;\n tags: Array<{\n key: string;\n value: string;\n }>;\n errors: Array<{\n type: string;\n message: string;\n }>;\n}\n\nfunction getRequiredOrg(): string {\n const org = getOrg();\n if (!org) {\n throw new Error(\n \"Sentry organization not configured. Please set SENTRY_ORG environment variable.\",\n );\n }\n return org;\n}\n\nasync function sentryFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const authToken = getApiKey() || process.env.SENTRY_AUTH_TOKEN;\n if (!authToken) {\n throw new Error(\"Not authenticated with Sentry. Please set SENTRY_AUTH_TOKEN.\");\n }\n\n const response = await fetch(`${SENTRY_API_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${authToken}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({}))) as { detail?: string };\n throw new Error(\n error.detail ?? `Sentry API error: ${response.status} ${response.statusText}`,\n );\n }\n\n return response.json();\n}\n\nexport function listOrganizations(): Promise<Organization[]> {\n return sentryFetch<Organization[]>(\"/organizations/\");\n}\n\nexport function listProjects(): Promise<Project[]> {\n const org = getRequiredOrg();\n return sentryFetch<Project[]>(`/organizations/${org}/projects/`);\n}\n\nexport function getProject(projectSlug: string): Promise<Project> {\n const org = getRequiredOrg();\n return sentryFetch<Project>(`/projects/${org}/${projectSlug}/`);\n}\n\nexport function listIssues(\n projectSlug: string,\n options: {\n query?: string;\n status?: \"resolved\" | \"unresolved\" | \"ignored\";\n sort?: \"date\" | \"new\" | \"freq\" | \"priority\" | \"user\";\n limit?: number;\n } = {},\n): Promise<Issue[]> {\n const org = getRequiredOrg();\n\n const params = new URLSearchParams({ project: projectSlug });\n\n if (options.query) params.append(\"query\", options.query);\n if (options.status) params.append(\"query\", `is:${options.status}`);\n if (options.sort) params.append(\"sort\", options.sort);\n if (options.limit) params.append(\"limit\", options.limit.toString());\n\n return sentryFetch<Issue[]>(`/organizations/${org}/issues/?${params.toString()}`);\n}\n\nexport function getIssue(issueId: string): Promise<Issue> {\n return sentryFetch<Issue>(`/issues/${issueId}/`);\n}\n\nexport function resolveIssue(issueId: string): Promise<Issue> {\n return sentryFetch<Issue>(`/issues/${issueId}/`, {\n method: \"PUT\",\n body: JSON.stringify({ status: \"resolved\" }),\n });\n}\n\nexport function listEvents(issueId: string, limit: number = 10): Promise<Event[]> {\n const params = new URLSearchParams({ limit: limit.toString() });\n return sentryFetch<Event[]>(`/issues/${issueId}/events/?${params.toString()}`);\n}\n",
|
|
305
|
+
"tools/list-issues.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listIssues } from \"../../lib/sentry-client.ts\";\n\nexport default tool({\n id: \"list-issues\",\n description:\n \"List issues/errors in a Sentry project with optional filters. Returns issue details including title, status, error count, and last seen date.\",\n inputSchema: z.object({\n projectSlug: z.string().describe(\"The slug of the project to list issues from\"),\n query: z.string().optional().describe(\"Search query to filter issues (e.g., 'is:unresolved')\"),\n status: z\n .enum([\"resolved\", \"unresolved\", \"ignored\"])\n .optional()\n .describe(\"Filter by issue status\"),\n sort: z\n .enum([\"date\", \"new\", \"freq\", \"priority\", \"user\"])\n .optional()\n .describe(\n \"Sort order: date (most recent), new (newest), freq (most frequent), priority, user (most users affected)\",\n ),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(25)\n .describe(\"Maximum number of issues to return (1-100)\"),\n }),\n async execute({ projectSlug, query, status, sort, limit }) {\n const issues = await listIssues(projectSlug, { query, status, sort, limit });\n\n return issues.map(\n ({\n id,\n shortId,\n title,\n culprit,\n permalink,\n level,\n status: issueStatus,\n substatus,\n platform,\n project,\n type,\n metadata,\n count,\n userCount,\n firstSeen,\n lastSeen,\n numComments,\n isBookmarked,\n isSubscribed,\n assignedTo,\n }) => ({\n id,\n shortId,\n title,\n culprit,\n permalink,\n level,\n status: issueStatus,\n substatus,\n platform,\n project: {\n id: project.id,\n name: project.name,\n slug: project.slug,\n },\n type,\n metadata,\n count,\n userCount,\n firstSeen,\n lastSeen,\n numComments,\n isBookmarked,\n isSubscribed,\n assignedTo,\n }),\n );\n },\n});\n",
|
|
306
|
+
"tools/resolve-issue.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { resolveIssue } from \"../../lib/sentry-client.ts\";\n\nexport default tool({\n id: \"resolve-issue\",\n description:\n \"Mark a Sentry issue as resolved. Use this after you've fixed a bug or determined an issue is no longer relevant.\",\n inputSchema: z.object({\n issueId: z.string().describe(\"The ID of the issue to resolve\"),\n }),\n async execute({ issueId }) {\n const issue = await resolveIssue(issueId);\n\n return {\n success: true,\n issue,\n message: `Issue ${issue.shortId} has been marked as resolved.`,\n };\n },\n});\n",
|
|
307
|
+
"tools/list-projects.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listProjects } from \"../../lib/sentry-client.ts\";\n\nexport default tool({\n id: \"list-projects\",\n description:\n \"List all projects in your Sentry organization. Returns project details including name, platform, status, and team information.\",\n inputSchema: z.object({}),\n async execute() {\n const projects = await listProjects();\n\n return projects.map(\n ({\n id,\n slug,\n name,\n platform,\n status,\n dateCreated,\n firstEvent,\n isBookmarked,\n isMember,\n hasAccess,\n teams,\n features,\n }) => ({\n id,\n slug,\n name,\n platform,\n status,\n dateCreated,\n firstEvent,\n isBookmarked,\n isMember,\n hasAccess,\n teams: teams.map(({ id, name, slug }) => ({ id, name, slug })),\n features,\n }),\n );\n },\n});\n",
|
|
308
|
+
"tools/get-issue.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getIssue, listEvents } from \"../../lib/sentry-client.ts\";\n\nexport default tool({\n id: \"get-issue\",\n description:\n \"Get detailed information about a specific Sentry issue including error details, stack traces, and recent events. Use this to investigate and debug specific errors.\",\n inputSchema: z.object({\n issueId: z.string().describe(\"The ID of the issue to retrieve\"),\n includeEvents: z\n .boolean()\n .default(true)\n .describe(\"Whether to include recent events for this issue\"),\n eventLimit: z\n .number()\n .min(1)\n .max(50)\n .default(5)\n .describe(\"Number of recent events to include (1-50)\"),\n }),\n async execute({ issueId, includeEvents, eventLimit }) {\n const issue = await getIssue(issueId);\n\n const events = includeEvents ? await listEvents(issueId, eventLimit) : [];\n\n return {\n issue: {\n id: issue.id,\n shortId: issue.shortId,\n title: issue.title,\n culprit: issue.culprit,\n permalink: issue.permalink,\n logger: issue.logger,\n level: issue.level,\n status: issue.status,\n substatus: issue.substatus,\n platform: issue.platform,\n project: {\n id: issue.project.id,\n name: issue.project.name,\n slug: issue.project.slug,\n platform: issue.project.platform,\n },\n type: issue.type,\n metadata: issue.metadata,\n count: issue.count,\n userCount: issue.userCount,\n firstSeen: issue.firstSeen,\n lastSeen: issue.lastSeen,\n numComments: issue.numComments,\n isBookmarked: issue.isBookmarked,\n isSubscribed: issue.isSubscribed,\n assignedTo: issue.assignedTo,\n stats: issue.stats,\n },\n events: events.map(\n ({\n id,\n eventID,\n message,\n platform,\n dateCreated,\n user,\n tags,\n contexts,\n entries,\n }) => ({\n id,\n eventID,\n message,\n platform,\n dateCreated,\n user,\n tags,\n contexts,\n entries,\n }),\n ),\n };\n },\n});\n",
|
|
309
|
+
".env.example": "# Sentry Integration\n# Create an Auth Token at https://sentry.io/settings/account/api/auth-tokens/\n# Find your organization slug in your Sentry URL: https://sentry.io/organizations/YOUR_ORG_SLUG/\n\nSENTRY_AUTH_TOKEN=your_auth_token_here\nSENTRY_ORG=your_organization_slug\n"
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
"integration:_base": {
|
|
313
|
+
"files": {
|
|
314
|
+
"SETUP.md": "# Integration Setup Guide\n\nThis guide helps you set up credentials for all 50+ service integrations available in Veryfront.\n\n## Quick Start\n\n```bash\n# Create a new project with integrations\nveryfront init my-app --with ai --integrations slack,github,notion\n\n# Start development\ncd my-app\nveryfront dev\n```\n\nVisit `http://localhost:3000/api/auth/{service}` to connect each service.\n\n---\n\n## Table of Contents\n\n- [Google Services](#google-services) (Gmail, Calendar, Drive, Docs, Sheets)\n- [Microsoft Services](#microsoft-services) (Outlook, Teams, SharePoint, OneDrive)\n- [Atlassian Services](#atlassian-services) (Jira, Confluence)\n- [Communication](#communication) (Slack, Discord, Twilio, Zoom, Webex)\n- [Project Management](#project-management) (Asana, Monday, Trello, ClickUp, Linear, Notion)\n- [Developer Tools](#developer-tools) (GitHub, GitLab, Bitbucket, Figma, Sentry, PostHog)\n- [CRM & Sales](#crm--sales) (Salesforce, HubSpot, Pipedrive, Intercom, Zendesk, Freshdesk)\n- [Databases](#databases) (Supabase, Neon, Airtable, Snowflake)\n- [Cloud & Storage](#cloud--storage) (AWS, Dropbox, Box)\n- [Finance](#finance) (Stripe, QuickBooks, Xero)\n- [Marketing](#marketing) (Mailchimp, Twitter)\n- [E-commerce](#e-commerce) (Shopify)\n- [AI & Analytics](#ai--analytics) (Anthropic, Mixpanel)\n\n---\n\n## Google Services\n\n**Gmail, Calendar, Drive, Docs, Sheets** all use the same Google OAuth credentials.\n\n### Setup Steps\n\n1. Go to [Google Cloud Console](https://console.cloud.google.com/apis/credentials)\n2. Create a new project or select existing\n3. Enable required APIs:\n - Gmail API\n - Google Calendar API\n - Google Drive API\n - Google Docs API\n - Google Sheets API\n4. Go to **OAuth consent screen**:\n - User Type: External (or Internal for Workspace)\n - Add scopes for each API you need\n5. Go to **Credentials** > **Create Credentials** > **OAuth client ID**:\n - Application type: Web application\n - Authorized redirect URIs:\n ```\n http://localhost:3000/api/auth/gmail/callback\n http://localhost:3000/api/auth/calendar/callback\n http://localhost:3000/api/auth/drive/callback\n http://localhost:3000/api/auth/docs-google/callback\n http://localhost:3000/api/auth/sheets/callback\n ```\n\n### Environment Variables\n\n```env\nGOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com\nGOOGLE_CLIENT_SECRET=your-client-secret\n```\n\n### Required Scopes by Service\n\n| Service | Scopes |\n|---------|--------|\n| Gmail | `gmail.readonly`, `gmail.send`, `gmail.modify` |\n| Calendar | `calendar.readonly`, `calendar.events` |\n| Drive | `drive.readonly`, `drive.file` |\n| Docs | `documents.readonly`, `documents` |\n| Sheets | `spreadsheets.readonly`, `spreadsheets` |\n\n---\n\n## Microsoft Services\n\n**Outlook, Teams, SharePoint, OneDrive** use Microsoft OAuth (Azure AD).\n\n### Setup Steps\n\n1. Go to [Azure Portal](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade)\n2. Click **New registration**:\n - Name: Your app name\n - Supported account types: Accounts in any organizational directory\n - Redirect URI: Web, `http://localhost:3000/api/auth/outlook/callback`\n3. After creation, go to **Certificates & secrets**:\n - Create a new client secret\n4. Go to **API permissions**:\n - Add Microsoft Graph permissions\n\n### Environment Variables\n\n```env\nMICROSOFT_CLIENT_ID=your-application-client-id\nMICROSOFT_CLIENT_SECRET=your-client-secret\nMICROSOFT_TENANT_ID=common\n```\n\n### Required Scopes by Service\n\n| Service | Scopes |\n|---------|--------|\n| Outlook | `Mail.Read`, `Mail.Send`, `Calendars.ReadWrite` |\n| Teams | `Team.ReadBasic.All`, `Chat.ReadWrite`, `ChannelMessage.Send` |\n| SharePoint | `Sites.Read.All`, `Files.ReadWrite.All` |\n| OneDrive | `Files.Read`, `Files.ReadWrite` |\n\n---\n\n## Atlassian Services\n\n**Jira and Confluence** use Atlassian OAuth 2.0 (3LO).\n\n### Setup Steps\n\n1. Go to [Atlassian Developer Console](https://developer.atlassian.com/console/myapps/)\n2. Click **Create** > **OAuth 2.0 integration**\n3. Configure:\n - Name: Your app name\n - Callback URL: `http://localhost:3000/api/auth/jira/callback`\n4. Add required scopes in **Permissions**\n5. Get your Cloud ID: Visit `https://your-domain.atlassian.net/_edge/tenant_info`\n\n### Environment Variables\n\n```env\nATLASSIAN_CLIENT_ID=your-client-id\nATLASSIAN_CLIENT_SECRET=your-client-secret\nATLASSIAN_CLOUD_ID=your-cloud-id\n```\n\n### Required Scopes\n\n| Service | Scopes |\n|---------|--------|\n| Jira | `read:jira-work`, `write:jira-work`, `read:jira-user` |\n| Confluence | `read:confluence-content.all`, `write:confluence-content` |\n\n---\n\n## Communication\n\n### Slack\n\n1. Go to [Slack API Apps](https://api.slack.com/apps)\n2. Click **Create New App** > **From scratch**\n3. Go to **OAuth & Permissions**:\n - Add redirect URL: `http://localhost:3000/api/auth/slack/callback`\n - Add scopes: `channels:read`, `chat:write`, `users:read`, `im:write`\n4. **Install to Workspace**\n\n```env\nSLACK_CLIENT_ID=your-client-id\nSLACK_CLIENT_SECRET=your-client-secret\n```\n\n### Discord\n\n1. Go to [Discord Developer Portal](https://discord.com/developers/applications)\n2. Create **New Application**\n3. Go to **OAuth2**:\n - Add redirect: `http://localhost:3000/api/auth/discord/callback`\n - Scopes: `identify`, `guilds`, `messages.read`\n\n```env\nDISCORD_CLIENT_ID=your-client-id\nDISCORD_CLIENT_SECRET=your-client-secret\n```\n\n### Twilio (SMS/WhatsApp)\n\n1. Go to [Twilio Console](https://console.twilio.com/)\n2. Get Account SID and Auth Token from dashboard\n3. Get or buy a phone number for sending\n\n```env\nTWILIO_ACCOUNT_SID=your-account-sid\nTWILIO_AUTH_TOKEN=your-auth-token\nTWILIO_PHONE_NUMBER=+1234567890\n```\n\n### Zoom\n\n1. Go to [Zoom App Marketplace](https://marketplace.zoom.us/develop/create)\n2. Create **OAuth App**\n3. Configure redirect: `http://localhost:3000/api/auth/zoom/callback`\n4. Add scopes: `meeting:read`, `meeting:write`, `user:read`\n\n```env\nZOOM_CLIENT_ID=your-client-id\nZOOM_CLIENT_SECRET=your-client-secret\n```\n\n### Webex\n\n1. Go to [Webex for Developers](https://developer.webex.com/my-apps)\n2. Create new integration\n3. Redirect URI: `http://localhost:3000/api/auth/webex/callback`\n4. Scopes: `spark:messages_read`, `spark:messages_write`, `spark:rooms_read`\n\n```env\nWEBEX_CLIENT_ID=your-client-id\nWEBEX_CLIENT_SECRET=your-client-secret\n```\n\n---\n\n## Project Management\n\n### Asana\n\n1. Go to [Asana Developer Console](https://app.asana.com/0/developer-console)\n2. Create new app\n3. Set redirect URL: `http://localhost:3000/api/auth/asana/callback`\n\n```env\nASANA_CLIENT_ID=your-client-id\nASANA_CLIENT_SECRET=your-client-secret\n```\n\n### Monday.com\n\n1. Go to [Monday Apps](https://auth.monday.com/oauth2/authorize)\n2. Create new app in your account's Developer section\n3. Configure OAuth with redirect: `http://localhost:3000/api/auth/monday/callback`\n\n```env\nMONDAY_CLIENT_ID=your-client-id\nMONDAY_CLIENT_SECRET=your-client-secret\n```\n\n### Trello\n\n1. Go to [Trello Power-Ups Admin](https://trello.com/power-ups/admin)\n2. Create new Power-Up\n3. Configure OAuth redirect: `http://localhost:3000/api/auth/trello/callback`\n\n```env\nTRELLO_API_KEY=your-api-key\nTRELLO_API_SECRET=your-api-secret\n```\n\n### ClickUp\n\n1. Go to [ClickUp API Settings](https://app.clickup.com/settings/apps)\n2. Create new app\n3. Redirect URL: `http://localhost:3000/api/auth/clickup/callback`\n\n```env\nCLICKUP_CLIENT_ID=your-client-id\nCLICKUP_CLIENT_SECRET=your-client-secret\n```\n\n### Linear\n\n1. Go to [Linear Settings > API](https://linear.app/settings/api)\n2. Create OAuth application\n3. Callback URL: `http://localhost:3000/api/auth/linear/callback`\n\n```env\nLINEAR_CLIENT_ID=your-client-id\nLINEAR_CLIENT_SECRET=your-client-secret\n```\n\n### Notion\n\n1. Go to [Notion Integrations](https://www.notion.so/my-integrations)\n2. Create new **public** integration (for OAuth)\n3. Set redirect URI: `http://localhost:3000/api/auth/notion/callback`\n4. **Important**: Share pages with your integration\n\n```env\nNOTION_CLIENT_ID=your-oauth-client-id\nNOTION_CLIENT_SECRET=your-oauth-client-secret\n```\n\n---\n\n## Developer Tools\n\n### GitHub\n\n1. Go to [GitHub Developer Settings](https://github.com/settings/developers)\n2. Create **New OAuth App**\n3. Authorization callback: `http://localhost:3000/api/auth/github/callback`\n\n```env\nGITHUB_CLIENT_ID=your-client-id\nGITHUB_CLIENT_SECRET=your-client-secret\n```\n\n### GitLab\n\n1. Go to [GitLab Applications](https://gitlab.com/-/profile/applications)\n2. Create new application\n3. Redirect URI: `http://localhost:3000/api/auth/gitlab/callback`\n4. Scopes: `read_user`, `read_api`, `read_repository`\n\n```env\nGITLAB_CLIENT_ID=your-application-id\nGITLAB_CLIENT_SECRET=your-secret\n```\n\n### Bitbucket\n\n1. Go to [Bitbucket App Passwords](https://bitbucket.org/account/settings/app-passwords/) or create OAuth consumer\n2. For OAuth: Workspace settings > OAuth consumers\n3. Callback URL: `http://localhost:3000/api/auth/bitbucket/callback`\n\n```env\nBITBUCKET_CLIENT_ID=your-client-id\nBITBUCKET_CLIENT_SECRET=your-client-secret\n```\n\n### Figma\n\n1. Go to [Figma Developers](https://www.figma.com/developers/apps)\n2. Create new app\n3. Callback URL: `http://localhost:3000/api/auth/figma/callback`\n\n```env\nFIGMA_CLIENT_ID=your-client-id\nFIGMA_CLIENT_SECRET=your-client-secret\n```\n\n### Sentry\n\n1. Go to [Sentry Developer Settings](https://sentry.io/settings/developer-settings/)\n2. Create new public integration\n3. Redirect URL: `http://localhost:3000/api/auth/sentry/callback`\n\n```env\nSENTRY_CLIENT_ID=your-client-id\nSENTRY_CLIENT_SECRET=your-client-secret\n```\n\n### PostHog\n\nUses API key authentication (no OAuth).\n\n1. Go to your PostHog project settings\n2. Create a personal API key\n\n```env\nPOSTHOG_API_KEY=phx_your-api-key\nPOSTHOG_HOST=https://app.posthog.com\n```\n\n---\n\n## CRM & Sales\n\n### Salesforce\n\n1. Go to [Salesforce Setup](https://login.salesforce.com/) > App Manager\n2. Create **New Connected App**\n3. Enable OAuth, add callback: `http://localhost:3000/api/auth/salesforce/callback`\n4. Required scopes: `api`, `refresh_token`\n\n```env\nSALESFORCE_CLIENT_ID=your-consumer-key\nSALESFORCE_CLIENT_SECRET=your-consumer-secret\n```\n\n### HubSpot\n\n1. Go to [HubSpot Developers](https://developers.hubspot.com/)\n2. Create app in your developer account\n3. Configure OAuth redirect: `http://localhost:3000/api/auth/hubspot/callback`\n4. Select required scopes\n\n```env\nHUBSPOT_CLIENT_ID=your-client-id\nHUBSPOT_CLIENT_SECRET=your-client-secret\n```\n\n### Pipedrive\n\n1. Go to [Pipedrive Marketplace Manager](https://developers.pipedrive.com/)\n2. Create new app\n3. OAuth redirect: `http://localhost:3000/api/auth/pipedrive/callback`\n\n```env\nPIPEDRIVE_CLIENT_ID=your-client-id\nPIPEDRIVE_CLIENT_SECRET=your-client-secret\n```\n\n### Intercom\n\n1. Go to [Intercom Developer Hub](https://developers.intercom.com/)\n2. Create new app\n3. Configure OAuth: `http://localhost:3000/api/auth/intercom/callback`\n\n```env\nINTERCOM_CLIENT_ID=your-client-id\nINTERCOM_CLIENT_SECRET=your-client-secret\n```\n\n### Zendesk\n\n1. Go to Admin Center > Apps and integrations > APIs > Zendesk API\n2. Create OAuth client\n3. Redirect URL: `http://localhost:3000/api/auth/zendesk/callback`\n\n```env\nZENDESK_CLIENT_ID=your-client-id\nZENDESK_CLIENT_SECRET=your-client-secret\nZENDESK_SUBDOMAIN=your-subdomain\n```\n\n### Freshdesk\n\nUses API key authentication.\n\n1. Go to Profile Settings in Freshdesk\n2. Find your API Key\n\n```env\nFRESHDESK_API_KEY=your-api-key\nFRESHDESK_DOMAIN=your-domain.freshdesk.com\n```\n\n---\n\n## Databases\n\n### Supabase\n\nUses API key (no OAuth needed).\n\n1. Go to your Supabase project dashboard\n2. Go to Settings > API\n3. Copy the `anon` or `service_role` key\n\n```env\nSUPABASE_URL=https://your-project.supabase.co\nSUPABASE_ANON_KEY=your-anon-key\nSUPABASE_SERVICE_ROLE_KEY=your-service-role-key\n```\n\n### Neon\n\nUses API key authentication.\n\n1. Go to [Neon Console](https://console.neon.tech/)\n2. Create API key in Account Settings\n\n```env\nNEON_API_KEY=your-api-key\nNEON_PROJECT_ID=your-project-id\n```\n\n### Airtable\n\n1. Go to [Airtable Account](https://airtable.com/account)\n2. Create personal access token or OAuth app\n3. For OAuth: [Airtable OAuth](https://airtable.com/create/oauth)\n\n```env\nAIRTABLE_API_KEY=your-api-key\n# Or for OAuth:\nAIRTABLE_CLIENT_ID=your-client-id\nAIRTABLE_CLIENT_SECRET=your-client-secret\n```\n\n### Snowflake\n\nUses account credentials (key-pair or password).\n\n1. Get your Snowflake account identifier\n2. Create a user with appropriate permissions\n3. (Optional) Set up key-pair authentication\n\n```env\nSNOWFLAKE_ACCOUNT=your-account-identifier\nSNOWFLAKE_USERNAME=your-username\nSNOWFLAKE_PASSWORD=your-password\nSNOWFLAKE_WAREHOUSE=your-warehouse\nSNOWFLAKE_DATABASE=your-database\n```\n\n---\n\n## Cloud & Storage\n\n### AWS\n\nUses IAM credentials.\n\n1. Go to [AWS IAM Console](https://console.aws.amazon.com/iam/)\n2. Create a new IAM user with programmatic access\n3. Attach policies for services you need (S3, EC2, Lambda, etc.)\n\n```env\nAWS_ACCESS_KEY_ID=your-access-key\nAWS_SECRET_ACCESS_KEY=your-secret-key\nAWS_REGION=us-east-1\n```\n\n### Dropbox\n\n1. Go to [Dropbox App Console](https://www.dropbox.com/developers/apps)\n2. Create app with Full Dropbox or App folder access\n3. OAuth2 redirect: `http://localhost:3000/api/auth/dropbox/callback`\n\n```env\nDROPBOX_CLIENT_ID=your-app-key\nDROPBOX_CLIENT_SECRET=your-app-secret\n```\n\n### Box\n\n1. Go to [Box Developer Console](https://app.box.com/developers/console)\n2. Create new app with OAuth 2.0\n3. Redirect URI: `http://localhost:3000/api/auth/box/callback`\n\n```env\nBOX_CLIENT_ID=your-client-id\nBOX_CLIENT_SECRET=your-client-secret\n```\n\n---\n\n## Finance\n\n### Stripe\n\nUses API key (no OAuth for basic usage).\n\n1. Go to [Stripe Dashboard](https://dashboard.stripe.com/apikeys)\n2. Get your secret key (use test key for development)\n\n```env\nSTRIPE_SECRET_KEY=sk_test_your-secret-key\nSTRIPE_PUBLISHABLE_KEY=pk_test_your-publishable-key\n```\n\n### QuickBooks\n\n1. Go to [Intuit Developer](https://developer.intuit.com/)\n2. Create app and get OAuth credentials\n3. Redirect URI: `http://localhost:3000/api/auth/quickbooks/callback`\n\n```env\nQUICKBOOKS_CLIENT_ID=your-client-id\nQUICKBOOKS_CLIENT_SECRET=your-client-secret\n```\n\n### Xero\n\n1. Go to [Xero Developer](https://developer.xero.com/app/manage)\n2. Create app\n3. Redirect URI: `http://localhost:3000/api/auth/xero/callback`\n\n```env\nXERO_CLIENT_ID=your-client-id\nXERO_CLIENT_SECRET=your-client-secret\n```\n\n---\n\n## Marketing\n\n### Mailchimp\n\n1. Go to [Mailchimp Account API Keys](https://us1.admin.mailchimp.com/account/api/)\n2. For OAuth: Register app at [Mailchimp OAuth](https://admin.mailchimp.com/account/oauth2/)\n3. Redirect: `http://localhost:3000/api/auth/mailchimp/callback`\n\n```env\nMAILCHIMP_CLIENT_ID=your-client-id\nMAILCHIMP_CLIENT_SECRET=your-client-secret\n# Or API key:\nMAILCHIMP_API_KEY=your-api-key-us1\n```\n\n### Twitter/X\n\n1. Go to [Twitter Developer Portal](https://developer.twitter.com/en/portal/dashboard)\n2. Create project and app\n3. Enable OAuth 2.0\n4. Callback URL: `http://localhost:3000/api/auth/twitter/callback`\n\n```env\nTWITTER_CLIENT_ID=your-client-id\nTWITTER_CLIENT_SECRET=your-client-secret\n```\n\n---\n\n## E-commerce\n\n### Shopify\n\n1. Go to [Shopify Partners](https://partners.shopify.com/)\n2. Create new app\n3. App URL and redirect: `http://localhost:3000/api/auth/shopify/callback`\n\n```env\nSHOPIFY_CLIENT_ID=your-api-key\nSHOPIFY_CLIENT_SECRET=your-api-secret\nSHOPIFY_SHOP_NAME=your-store.myshopify.com\n```\n\n---\n\n## AI & Analytics\n\n### Anthropic (Admin API)\n\nFor organization management and usage tracking.\n\n1. Go to [Anthropic Console](https://console.anthropic.com/)\n2. Create Admin API key (requires admin access)\n\n```env\nANTHROPIC_ADMIN_API_KEY=your-admin-api-key\n```\n\n### Mixpanel\n\nUses API key/secret for data export.\n\n1. Go to [Mixpanel Project Settings](https://mixpanel.com/settings/project)\n2. Get Project Token for tracking\n3. Get API Secret for data export\n\n```env\nMIXPANEL_PROJECT_TOKEN=your-project-token\nMIXPANEL_API_SECRET=your-api-secret\n```\n\n---\n\n## Testing Your Setup\n\nAfter configuring credentials:\n\n```bash\n# Start the dev server\nveryfront dev\n\n# Test each integration by visiting:\n# http://localhost:3000/api/auth/{service}\n\n# Check connection status\ncurl http://localhost:3000/api/connections\n```\n\n## Troubleshooting\n\n### Common Issues\n\n| Error | Solution |\n|-------|----------|\n| \"Invalid redirect URI\" | Ensure callback URL matches exactly (including trailing slash) |\n| \"Invalid client\" | Check CLIENT_ID is correct and app is published |\n| \"Access denied\" | Verify all required scopes are added |\n| \"Token expired\" | Implement refresh token flow or re-authenticate |\n\n### Debug Mode\n\nEnable debug logging:\n\n```bash\nDEBUG=veryfront:oauth veryfront dev\n```\n\n### Token Storage\n\nBy default, tokens are stored in memory. For production:\n\n1. Implement `TokenStore` interface in `lib/token-store.ts`\n2. Use Redis, database, or encrypted file storage\n3. Handle token refresh automatically\n\n## Production Checklist\n\n- [ ] Update all redirect URIs to production domain\n- [ ] Implement persistent token storage\n- [ ] Set up token encryption\n- [ ] Configure rate limiting\n- [ ] Add error monitoring (Sentry)\n- [ ] Test OAuth flows end-to-end\n- [ ] Review and minimize required scopes\n\n## Need Help?\n\n- Run `veryfront doctor` to diagnose issues\n- Check the [Veryfront Documentation](https://veryfront.com/docs)\n- Join our [Discord community](https://discord.gg/veryfront)\n",
|
|
315
|
+
"lib/token-store.ts": "/********************************************************************************\n * OAuth Token Store\n *\n * Manages OAuth tokens for connected services.\n *\n * ## Storage Modes\n *\n * **Development (default)**: In-memory storage - tokens are lost on restart.\n * **Production**: Configure via environment variables:\n * - DATABASE_URL: Uses database storage (Postgres, SQLite, MySQL)\n * - KV_REST_API_URL + KV_REST_API_TOKEN: Uses Vercel KV\n * - REDIS_URL: Uses Redis\n * - TOKEN_ENCRYPTION_KEY: Enables AES-256-GCM encryption (recommended)\n *\n * ## Security\n *\n * Tokens contain sensitive OAuth credentials. In production:\n * 1. Always use encrypted storage (set TOKEN_ENCRYPTION_KEY)\n * 2. Use HTTPS for all connections\n * 3. Implement proper access control\n * 4. Rotate encryption keys periodically\n *\n * @see lib/token-store-examples.ts for complete production implementations\n ********************************************************************************/\n\nexport interface OAuthToken {\n accessToken: string;\n refreshToken?: string;\n expiresAt?: number;\n tokenType?: string;\n scope?: string;\n}\n\nexport interface TokenStore {\n getToken(userId: string, service: string): Promise<OAuthToken | null>;\n setToken(userId: string, service: string, token: OAuthToken): Promise<void>;\n revokeToken(userId: string, service: string): Promise<void>;\n isConnected(userId: string, service: string): Promise<boolean>;\n}\n\n/** Token store configuration for production backends */\nexport interface TokenStoreConfig {\n get: (key: string) => Promise<string | null>;\n set: (key: string, value: string) => Promise<void>;\n delete: (key: string) => Promise<void>;\n}\n\n// ============================================================================\n// Encryption Utilities\n// ============================================================================\n\nexport async function encryptToken(token: OAuthToken): Promise<string> {\n const key = getEncryptionKey();\n if (!key) return JSON.stringify(token);\n\n const data = new TextEncoder().encode(JSON.stringify(token));\n const iv = crypto.getRandomValues(new Uint8Array(12));\n\n const cryptoKey = await crypto.subtle.importKey(\"raw\", key, { name: \"AES-GCM\" }, false, [\n \"encrypt\",\n ]);\n\n const encrypted = await crypto.subtle.encrypt({ name: \"AES-GCM\", iv }, cryptoKey, data);\n\n const combined = new Uint8Array(iv.length + encrypted.byteLength);\n combined.set(iv);\n combined.set(new Uint8Array(encrypted), iv.length);\n\n return `encrypted:${btoa(String.fromCharCode(...combined))}`;\n}\n\nexport async function decryptToken(encrypted: string): Promise<OAuthToken | null> {\n if (!encrypted.startsWith(\"encrypted:\")) {\n try {\n return JSON.parse(encrypted) as OAuthToken;\n } catch {\n return null;\n }\n }\n\n const key = getEncryptionKey();\n if (!key) {\n console.error(\"[Token Store] Cannot decrypt: TOKEN_ENCRYPTION_KEY not set\");\n return null;\n }\n\n try {\n const base64 = encrypted.slice(\"encrypted:\".length);\n const combined = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));\n\n const iv = combined.slice(0, 12);\n const ciphertext = combined.slice(12);\n\n const cryptoKey = await crypto.subtle.importKey(\"raw\", key, { name: \"AES-GCM\" }, false, [\n \"decrypt\",\n ]);\n\n const decrypted = await crypto.subtle.decrypt({ name: \"AES-GCM\", iv }, cryptoKey, ciphertext);\n\n return JSON.parse(new TextDecoder().decode(decrypted)) as OAuthToken;\n } catch (error) {\n console.error(\"[Token Store] Decryption failed:\", error);\n return null;\n }\n}\n\nconst AUTO_KEY_STORAGE = \"__veryfront_auto_encryption_key__\";\nconst TOKENS_KEY = \"__veryfront_oauth_tokens__\";\n\nconst globalStore = globalThis as Record<string, unknown>;\n\nexport function generateEncryptionKey(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\nfunction getEnvVar(name: string): string | undefined {\n if (typeof process !== \"undefined\") return process.env?.[name];\n return (globalThis as any).Deno?.env?.get(name);\n}\n\nfunction hexToKeyBytes(keyHex: string): Uint8Array | null {\n if (keyHex.length !== 64) {\n console.error(\"[Token Store] TOKEN_ENCRYPTION_KEY must be 64 hex characters (32 bytes)\");\n return null;\n }\n\n const key = new Uint8Array(32);\n for (let i = 0; i < 32; i++) {\n key[i] = parseInt(keyHex.slice(i * 2, i * 2 + 2), 16);\n }\n return key;\n}\n\n/** Get encryption key from environment or auto-generate for development */\nfunction getEncryptionKey(): Uint8Array | null {\n const keyHex = getEnvVar(\"TOKEN_ENCRYPTION_KEY\");\n if (keyHex) return hexToKeyBytes(keyHex);\n\n if (!globalStore[AUTO_KEY_STORAGE]) {\n globalStore[AUTO_KEY_STORAGE] = generateEncryptionKey();\n console.log(\"[Token Store] Auto-generated encryption key for this session\");\n }\n\n return hexToKeyBytes(globalStore[AUTO_KEY_STORAGE] as string);\n}\n\n// ============================================================================\n// Storage Mode Detection\n// ============================================================================\n\nexport type StorageMode = \"memory\" | \"database\" | \"kv\" | \"redis\" | \"custom\";\n\nexport function getStorageMode(): StorageMode {\n const env =\n typeof process !== \"undefined\"\n ? process.env\n : ((globalThis as any).Deno?.env?.toObject() as Record<string, string> | undefined) ?? {};\n\n if (env.DATABASE_URL) return \"database\";\n if (env.KV_REST_API_URL) return \"kv\";\n if (env.REDIS_URL) return \"redis\";\n return \"memory\";\n}\n\nexport function isEncryptionEnabled(): boolean {\n return getEncryptionKey() !== null;\n}\n\n// ============================================================================\n// In-Memory Store (Development)\n// ============================================================================\n\nconst tokens = (globalStore[TOKENS_KEY] as Map<string, OAuthToken> | undefined) ?? new Map();\nglobalStore[TOKENS_KEY] = tokens;\n\nfunction getKey(userId: string, service: string): string {\n return `${userId}:${service}`;\n}\n\nasync function isConnected(store: Pick<TokenStore, \"getToken\">, userId: string, service: string): Promise<boolean> {\n const token = await store.getToken(userId, service);\n return !!token && (!token.expiresAt || token.expiresAt > Date.now());\n}\n\nconst inMemoryStore: TokenStore = {\n async getToken(userId: string, service: string): Promise<OAuthToken | null> {\n return tokens.get(getKey(userId, service)) ?? null;\n },\n\n async setToken(userId: string, service: string, token: OAuthToken): Promise<void> {\n tokens.set(getKey(userId, service), token);\n },\n\n async revokeToken(userId: string, service: string): Promise<void> {\n tokens.delete(getKey(userId, service));\n },\n\n async isConnected(userId: string, service: string): Promise<boolean> {\n return isConnected(this, userId, service);\n },\n};\n\n// ============================================================================\n// Token Store Factory\n// ============================================================================\n\nexport function createTokenStore(config: TokenStoreConfig): TokenStore {\n return {\n async getToken(userId: string, service: string): Promise<OAuthToken | null> {\n const data = await config.get(getKey(userId, service));\n if (!data) return null;\n return decryptToken(data);\n },\n\n async setToken(userId: string, service: string, token: OAuthToken): Promise<void> {\n await config.set(getKey(userId, service), await encryptToken(token));\n },\n\n async revokeToken(userId: string, service: string): Promise<void> {\n await config.delete(getKey(userId, service));\n },\n\n async isConnected(userId: string, service: string): Promise<boolean> {\n return isConnected(this, userId, service);\n },\n };\n}\n\n// ============================================================================\n// Default Export (Auto-detects environment)\n// ============================================================================\n\nexport const tokenStore: TokenStore = inMemoryStore;\n\nif (typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\") {\n if (getStorageMode() === \"memory\") {\n console.warn(\n \"[Token Store] Using in-memory storage (development mode). \" +\n \"Tokens will be lost on restart. \" +\n \"Set DATABASE_URL, KV_REST_API_URL, or REDIS_URL for production.\",\n );\n }\n}\n",
|
|
316
|
+
"lib/token-store-examples.ts": "/**\n * Production Token Store Examples\n *\n * Copy-paste implementations for different storage backends.\n * Each example includes encryption support via TOKEN_ENCRYPTION_KEY.\n *\n * @module\n */\n\nimport { createTokenStore, tokenStore, type TokenStore } from \"./token-store.ts\";\n\n// ============================================================================\n// Vercel KV Store\n// ============================================================================\n\n/**\n * Token store using Vercel KV (Redis-compatible)\n *\n * Required environment variables:\n * - KV_REST_API_URL\n * - KV_REST_API_TOKEN\n * - TOKEN_ENCRYPTION_KEY (recommended)\n *\n * @example\n * ```typescript\n * // lib/token-store.ts\n * import { createVercelKVStore } from './token-store-examples';\n * export const tokenStore = createVercelKVStore();\n * ```\n */\nexport function createVercelKVStore(): TokenStore {\n let kvPromise: Promise<typeof import(\"@vercel/kv\")> | null = null;\n\n async function getKV() {\n kvPromise ??= import(\"@vercel/kv\");\n return (await kvPromise).kv;\n }\n\n return createTokenStore({\n async get(key: string): Promise<string | null> {\n const kv = await getKV();\n return kv.get<string>(key);\n },\n async set(key: string, value: string): Promise<void> {\n const kv = await getKV();\n await kv.set(key, value);\n },\n async delete(key: string): Promise<void> {\n const kv = await getKV();\n await kv.del(key);\n },\n });\n}\n\n// ============================================================================\n// Redis Store\n// ============================================================================\n\n/**\n * Token store using Redis\n *\n * Required environment variables:\n * - REDIS_URL (e.g., redis://localhost:6379)\n * - TOKEN_ENCRYPTION_KEY (recommended)\n *\n * @example\n * ```typescript\n * // lib/token-store.ts\n * import { createRedisStore } from './token-store-examples';\n * export const tokenStore = createRedisStore();\n * ```\n */\nexport function createRedisStore(): TokenStore {\n let clientPromise: Promise<ReturnType<typeof import(\"redis\").createClient>> | null = null;\n\n async function getClient() {\n clientPromise ??= (async () => {\n const { createClient } = await import(\"redis\");\n const client = createClient({ url: process.env.REDIS_URL });\n await client.connect();\n return client;\n })();\n\n return clientPromise;\n }\n\n return createTokenStore({\n async get(key: string): Promise<string | null> {\n const client = await getClient();\n return client.get(key);\n },\n async set(key: string, value: string): Promise<void> {\n const client = await getClient();\n await client.set(key, value);\n },\n async delete(key: string): Promise<void> {\n const client = await getClient();\n await client.del(key);\n },\n });\n}\n\n// ============================================================================\n// PostgreSQL Store\n// ============================================================================\n\n/**\n * Token store using PostgreSQL\n *\n * Required environment variables:\n * - DATABASE_URL (e.g., postgres://user:pass@host:5432/db)\n * - TOKEN_ENCRYPTION_KEY (recommended)\n *\n * Required table (create with migration):\n * ```sql\n * CREATE TABLE oauth_tokens (\n * key VARCHAR(255) PRIMARY KEY,\n * value TEXT NOT NULL,\n * created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n * updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n * );\n * CREATE INDEX idx_oauth_tokens_key ON oauth_tokens(key);\n * ```\n *\n * @example\n * ```typescript\n * // lib/token-store.ts\n * import { createPostgresStore } from './token-store-examples';\n * export const tokenStore = createPostgresStore();\n * ```\n */\nexport function createPostgresStore(): TokenStore {\n let poolPromise: Promise<import(\"pg\").Pool> | null = null;\n\n async function getPool() {\n poolPromise ??= (async () => {\n const { Pool } = await import(\"pg\");\n return new Pool({ connectionString: process.env.DATABASE_URL });\n })();\n\n return poolPromise;\n }\n\n return createTokenStore({\n async get(key: string): Promise<string | null> {\n const pool = await getPool();\n const result = await pool.query(\"SELECT value FROM oauth_tokens WHERE key = $1\", [key]);\n return result.rows[0]?.value ?? null;\n },\n async set(key: string, value: string): Promise<void> {\n const pool = await getPool();\n await pool.query(\n `INSERT INTO oauth_tokens (key, value, updated_at)\n VALUES ($1, $2, NOW())\n ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW()`,\n [key, value]\n );\n },\n async delete(key: string): Promise<void> {\n const pool = await getPool();\n await pool.query(\"DELETE FROM oauth_tokens WHERE key = $1\", [key]);\n },\n });\n}\n\n// ============================================================================\n// SQLite Store (for edge/serverless with D1, Turso, etc.)\n// ============================================================================\n\n/**\n * Token store using SQLite (Cloudflare D1, Turso, better-sqlite3)\n *\n * Required table:\n * ```sql\n * CREATE TABLE oauth_tokens (\n * key TEXT PRIMARY KEY,\n * value TEXT NOT NULL,\n * updated_at INTEGER DEFAULT (strftime('%s', 'now'))\n * );\n * ```\n *\n * @param db - SQLite database instance (D1Database, Connection, or Database)\n *\n * @example With Cloudflare D1\n * ```typescript\n * // In your API route\n * export async function GET(request: Request, { env }) {\n * const tokenStore = createSQLiteStore(env.DB);\n * // ...\n * }\n * ```\n *\n * @example With Turso\n * ```typescript\n * import { createClient } from '@libsql/client';\n * const db = createClient({ url: process.env.TURSO_URL, authToken: process.env.TURSO_AUTH_TOKEN });\n * export const tokenStore = createSQLiteStore(db);\n * ```\n */\nexport function createSQLiteStore(db: {\n prepare(sql: string): {\n bind(...args: unknown[]): { first(): Promise<{ value?: string } | null>; run(): Promise<void> };\n };\n}): TokenStore {\n return createTokenStore({\n async get(key: string): Promise<string | null> {\n const result = await db.prepare(\"SELECT value FROM oauth_tokens WHERE key = ?\").bind(key).first();\n return result?.value ?? null;\n },\n async set(key: string, value: string): Promise<void> {\n await db\n .prepare(\n `INSERT OR REPLACE INTO oauth_tokens (key, value, updated_at)\n VALUES (?, ?, strftime('%s', 'now'))`\n )\n .bind(key, value)\n .run();\n },\n async delete(key: string): Promise<void> {\n await db.prepare(\"DELETE FROM oauth_tokens WHERE key = ?\").bind(key).run();\n },\n });\n}\n\n// ============================================================================\n// Cloudflare Workers KV Store\n// ============================================================================\n\n/**\n * Token store using Cloudflare Workers KV\n *\n * @param kv - KV namespace binding from worker environment\n *\n * @example\n * ```typescript\n * // In your worker\n * export default {\n * async fetch(request, env) {\n * const tokenStore = createWorkersKVStore(env.OAUTH_TOKENS);\n * // ...\n * }\n * };\n * ```\n */\nexport function createWorkersKVStore(kv: {\n get(key: string): Promise<string | null>;\n put(key: string, value: string): Promise<void>;\n delete(key: string): Promise<void>;\n}): TokenStore {\n return createTokenStore({\n get: (key) => kv.get(key),\n set: (key, value) => kv.put(key, value),\n delete: (key) => kv.delete(key),\n });\n}\n\n// ============================================================================\n// Prisma Store\n// ============================================================================\n\n/**\n * Token store using Prisma ORM\n *\n * Required Prisma schema:\n * ```prisma\n * model OAuthToken {\n * key String @id\n * value String\n * updatedAt DateTime @updatedAt\n * }\n * ```\n *\n * @example\n * ```typescript\n * import { PrismaClient } from '@prisma/client';\n * const prisma = new PrismaClient();\n * export const tokenStore = createPrismaStore(prisma);\n * ```\n */\nexport function createPrismaStore(prisma: {\n oAuthToken: {\n findUnique(args: { where: { key: string } }): Promise<{ value: string } | null>;\n upsert(args: {\n where: { key: string };\n update: { value: string };\n create: { key: string; value: string };\n }): Promise<unknown>;\n delete(args: { where: { key: string } }): Promise<unknown>;\n };\n}): TokenStore {\n return createTokenStore({\n async get(key: string): Promise<string | null> {\n const record = await prisma.oAuthToken.findUnique({ where: { key } });\n return record?.value ?? null;\n },\n async set(key: string, value: string): Promise<void> {\n await prisma.oAuthToken.upsert({\n where: { key },\n update: { value },\n create: { key, value },\n });\n },\n async delete(key: string): Promise<void> {\n try {\n await prisma.oAuthToken.delete({ where: { key } });\n } catch {\n // Ignore if not found\n }\n },\n });\n}\n\n// ============================================================================\n// Drizzle ORM Store\n// ============================================================================\n\n/**\n * Token store using Drizzle ORM\n *\n * Required schema:\n * ```typescript\n * import { pgTable, text, timestamp } from 'drizzle-orm/pg-core';\n *\n * export const oauthTokens = pgTable('oauth_tokens', {\n * key: text('key').primaryKey(),\n * value: text('value').notNull(),\n * updatedAt: timestamp('updated_at').defaultNow(),\n * });\n * ```\n *\n * @example\n * ```typescript\n * import { drizzle } from 'drizzle-orm/postgres-js';\n * import postgres from 'postgres';\n * import { oauthTokens } from './schema';\n *\n * const client = postgres(process.env.DATABASE_URL!);\n * const db = drizzle(client);\n * export const tokenStore = createDrizzleStore(db, oauthTokens);\n * ```\n */\nexport function createDrizzleStore<T extends { key: unknown; value: unknown }>(\n db: {\n select(): {\n from(table: T): { where(condition: unknown): { get(): Promise<{ value: string } | undefined> } };\n };\n insert(table: T): {\n values(data: { key: string; value: string }): {\n onConflictDoUpdate(args: { target: unknown; set: { value: string } }): { execute(): Promise<void> };\n };\n };\n delete(table: T): { where(condition: unknown): { execute(): Promise<void> } };\n },\n table: T & { key: unknown; value: unknown },\n eq: (col: unknown, val: unknown) => unknown\n): TokenStore {\n return createTokenStore({\n async get(key: string): Promise<string | null> {\n const result = await db.select().from(table).where(eq(table.key, key)).get();\n return result?.value ?? null;\n },\n async set(key: string, value: string): Promise<void> {\n await db\n .insert(table)\n .values({ key, value })\n .onConflictDoUpdate({ target: table.key, set: { value } })\n .execute();\n },\n async delete(key: string): Promise<void> {\n await db.delete(table).where(eq(table.key, key)).execute();\n },\n });\n}\n\n// ============================================================================\n// Auto-Select Store (Recommended)\n// ============================================================================\n\n/**\n * Automatically selects the appropriate token store based on environment\n *\n * Detection order:\n * 1. DATABASE_URL -> PostgreSQL\n * 2. KV_REST_API_URL -> Vercel KV\n * 3. REDIS_URL -> Redis\n * 4. Fallback -> In-memory (development only)\n *\n * @example\n * ```typescript\n * // lib/token-store.ts\n * import { createAutoStore } from './token-store-examples';\n * export const tokenStore = createAutoStore();\n * ```\n */\nexport function createAutoStore(): TokenStore {\n const env = process.env;\n\n if (env.DATABASE_URL) {\n console.log(\"[Token Store] Using PostgreSQL storage\");\n return createPostgresStore();\n }\n\n if (env.KV_REST_API_URL && env.KV_REST_API_TOKEN) {\n console.log(\"[Token Store] Using Vercel KV storage\");\n return createVercelKVStore();\n }\n\n if (env.REDIS_URL) {\n console.log(\"[Token Store] Using Redis storage\");\n return createRedisStore();\n }\n\n console.warn(\n \"[Token Store] No production storage configured. \" +\n \"Using in-memory storage (tokens will be lost on restart). \" +\n \"Set DATABASE_URL, KV_REST_API_URL, or REDIS_URL for production.\"\n );\n\n return tokenStore;\n}\n",
|
|
317
|
+
"lib/oauth.ts": "import { type OAuthToken, tokenStore } from \"./token-store.ts\";\n\nexport interface OAuthProvider {\n name: string;\n authorizationUrl: string;\n tokenUrl: string;\n clientId: string;\n clientSecret: string;\n scopes: string[];\n callbackPath: string;\n}\n\nfunction getExpiresAt(expiresIn: unknown): number | undefined {\n if (typeof expiresIn !== \"number\" || !expiresIn) return undefined;\n return Date.now() + expiresIn * 1000;\n}\n\nasync function postTokenRequest(\n provider: OAuthProvider,\n body: Record<string, string>,\n errorPrefix: string,\n): Promise<any> {\n const response = await fetch(provider.tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams(body),\n });\n\n if (response.ok) return response.json();\n\n const error = await response.text();\n throw new Error(`${errorPrefix}: ${response.status} - ${error}`);\n}\n\nexport function getAuthorizationUrl(\n provider: OAuthProvider,\n state: string,\n redirectUri: string,\n): string {\n const params = new URLSearchParams({\n client_id: provider.clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: provider.scopes.join(\" \"),\n state,\n access_type: \"offline\",\n prompt: \"consent\",\n });\n\n return `${provider.authorizationUrl}?${params.toString()}`;\n}\n\nexport async function exchangeCodeForTokens(\n provider: OAuthProvider,\n code: string,\n redirectUri: string,\n): Promise<OAuthToken> {\n const data = await postTokenRequest(\n provider,\n {\n client_id: provider.clientId,\n client_secret: provider.clientSecret,\n code,\n grant_type: \"authorization_code\",\n redirect_uri: redirectUri,\n },\n \"Token exchange failed\",\n );\n\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: getExpiresAt(data.expires_in),\n tokenType: data.token_type || \"Bearer\",\n scope: data.scope,\n };\n}\n\nexport async function refreshAccessToken(\n provider: OAuthProvider,\n refreshToken: string,\n): Promise<OAuthToken> {\n const data = await postTokenRequest(\n provider,\n {\n client_id: provider.clientId,\n client_secret: provider.clientSecret,\n refresh_token: refreshToken,\n grant_type: \"refresh_token\",\n },\n \"Token refresh failed\",\n );\n\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token || refreshToken,\n expiresAt: getExpiresAt(data.expires_in),\n tokenType: data.token_type || \"Bearer\",\n scope: data.scope,\n };\n}\n\nexport async function getValidToken(\n provider: OAuthProvider,\n userId: string,\n service: string,\n): Promise<string | null> {\n const token = await tokenStore.getToken(userId, service);\n if (!token) return null;\n\n const isExpired = token.expiresAt\n ? token.expiresAt < Date.now() + 5 * 60 * 1000\n : false;\n\n if (!isExpired || !token.refreshToken) return token.accessToken;\n\n try {\n const newToken = await refreshAccessToken(provider, token.refreshToken);\n await tokenStore.setToken(userId, service, newToken);\n return newToken.accessToken;\n } catch {\n await tokenStore.revokeToken(userId, service);\n return null;\n }\n}\n",
|
|
318
|
+
"app/api/integrations/token-storage/route.ts": "/**\n * Token Storage Status API\n *\n * Returns the current token storage mode and encryption status.\n * This endpoint is self-contained to work with any version of token-store.\n */\nexport async function GET(): Promise<Response> {\n const env = process.env;\n\n let mode: \"memory\" | \"database\" | \"kv\" | \"redis\" = \"memory\";\n if (env.DATABASE_URL) mode = \"database\";\n else if (env.KV_REST_API_URL) mode = \"kv\";\n else if (env.REDIS_URL) mode = \"redis\";\n\n const encryptionKey = env.TOKEN_ENCRYPTION_KEY;\n const hasExplicitKey = encryptionKey?.length === 64;\n\n return Response.json({\n mode,\n encrypted: true,\n autoGenerated: !hasExplicitKey,\n });\n}\n",
|
|
319
|
+
"app/api/integrations/status/route.ts": "import { tokenStore } from \"../../../../lib/token-store.ts\";\n\nconst INTEGRATIONS = [\n { id: \"gmail\", name: \"Gmail\", icon: \"mail\" },\n { id: \"slack\", name: \"Slack\", icon: \"slack\" },\n { id: \"calendar\", name: \"Calendar\", icon: \"calendar\" },\n { id: \"github\", name: \"GitHub\", icon: \"github\" },\n { id: \"jira\", name: \"Jira\", icon: \"jira\" },\n { id: \"notion\", name: \"Notion\", icon: \"notion\" },\n];\n\nexport async function GET(_req: Request): Promise<Response> {\n const userId = \"current-user\";\n\n const integrations = await Promise.all(\n INTEGRATIONS.map(async ({ id, name, icon }) => ({\n id,\n name,\n icon,\n connected: await tokenStore.isConnected(userId, id),\n connectUrl: `/api/auth/${id}`,\n })),\n );\n\n return Response.json({ integrations });\n}\n",
|
|
320
|
+
"app/setup/page.tsx": "\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\n\ninterface Integration {\n id: string;\n name: string;\n icon: string;\n connected: boolean;\n connectUrl: string;\n}\n\ninterface SetupStep {\n id: string;\n title: string;\n description: string;\n completed: boolean;\n action?: () => void;\n link?: string;\n}\n\ninterface SetupGuide {\n title: string;\n steps: string[];\n link: string;\n envVars: string[];\n category: string;\n}\n\ninterface TokenStorageStatus {\n mode: \"memory\" | \"database\" | \"kv\" | \"redis\" | \"custom\";\n encrypted: boolean;\n autoGenerated?: boolean;\n}\n\nconst CATEGORIES = [\n { id: \"google\", name: \"Google Services\", icon: \"google\" },\n { id: \"microsoft\", name: \"Microsoft Services\", icon: \"microsoft\" },\n { id: \"atlassian\", name: \"Atlassian\", icon: \"atlassian\" },\n { id: \"communication\", name: \"Communication\", icon: \"chat\" },\n { id: \"development\", name: \"Development\", icon: \"code\" },\n { id: \"productivity\", name: \"Productivity\", icon: \"tasks\" },\n { id: \"storage\", name: \"Storage\", icon: \"folder\" },\n { id: \"infrastructure\", name: \"Infrastructure\", icon: \"server\" },\n { id: \"sales\", name: \"Sales & CRM\", icon: \"users\" },\n { id: \"support\", name: \"Support\", icon: \"headset\" },\n { id: \"finance\", name: \"Finance\", icon: \"dollar\" },\n { id: \"marketing\", name: \"Marketing\", icon: \"megaphone\" },\n { id: \"design\", name: \"Design\", icon: \"palette\" },\n { id: \"ai\", name: \"AI Providers\", icon: \"brain\" },\n] as const;\n\nconst OAUTH_SETUP_GUIDES: Record<string, SetupGuide> = {\n gmail: {\n title: \"Google OAuth Setup (Gmail)\",\n category: \"google\",\n steps: [\n \"Go to Google Cloud Console\",\n \"Create a new project or select existing one\",\n \"Enable Gmail API in APIs & Services > Library\",\n \"Go to APIs & Services > Credentials\",\n \"Create OAuth 2.0 credentials (Web application)\",\n \"Add redirect URI: http://localhost:3000/api/auth/gmail/callback\",\n \"Copy Client ID and Secret to your .env file\",\n ],\n link: \"https://console.cloud.google.com/apis/credentials\",\n envVars: [\"GOOGLE_CLIENT_ID\", \"GOOGLE_CLIENT_SECRET\"],\n },\n calendar: {\n title: \"Google Calendar Setup\",\n category: \"google\",\n steps: [\n \"Uses same Google OAuth credentials as Gmail\",\n \"Enable Calendar API in Google Cloud Console\",\n \"Add redirect URI: http://localhost:3000/api/auth/calendar/callback\",\n ],\n link: \"https://console.cloud.google.com/apis/library/calendar-json.googleapis.com\",\n envVars: [\"GOOGLE_CLIENT_ID\", \"GOOGLE_CLIENT_SECRET\"],\n },\n drive: {\n title: \"Google Drive Setup\",\n category: \"google\",\n steps: [\n \"Uses same Google OAuth credentials\",\n \"Enable Drive API in Google Cloud Console\",\n \"Add redirect URI: http://localhost:3000/api/auth/drive/callback\",\n ],\n link: \"https://console.cloud.google.com/apis/library/drive.googleapis.com\",\n envVars: [\"GOOGLE_CLIENT_ID\", \"GOOGLE_CLIENT_SECRET\"],\n },\n sheets: {\n title: \"Google Sheets Setup\",\n category: \"google\",\n steps: [\n \"Uses same Google OAuth credentials\",\n \"Enable Sheets API in Google Cloud Console\",\n \"Add redirect URI: http://localhost:3000/api/auth/sheets/callback\",\n ],\n link: \"https://console.cloud.google.com/apis/library/sheets.googleapis.com\",\n envVars: [\"GOOGLE_CLIENT_ID\", \"GOOGLE_CLIENT_SECRET\"],\n },\n \"docs-google\": {\n title: \"Google Docs Setup\",\n category: \"google\",\n steps: [\n \"Uses same Google OAuth credentials\",\n \"Enable Docs API in Google Cloud Console\",\n \"Add redirect URI: http://localhost:3000/api/auth/docs-google/callback\",\n ],\n link: \"https://console.cloud.google.com/apis/library/docs.googleapis.com\",\n envVars: [\"GOOGLE_CLIENT_ID\", \"GOOGLE_CLIENT_SECRET\"],\n },\n\n outlook: {\n title: \"Microsoft Outlook Setup\",\n category: \"microsoft\",\n steps: [\n \"Go to Azure Portal > Azure Active Directory\",\n \"Click App registrations > New registration\",\n \"Set redirect URI: http://localhost:3000/api/auth/outlook/callback\",\n \"Go to API permissions > Add Microsoft Graph permissions\",\n \"Add: Mail.Read, Mail.Send, Mail.ReadWrite\",\n \"Go to Certificates & secrets > New client secret\",\n \"Copy Application ID and Secret to .env\",\n ],\n link: \"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\",\n envVars: [\"MICROSOFT_CLIENT_ID\", \"MICROSOFT_CLIENT_SECRET\"],\n },\n teams: {\n title: \"Microsoft Teams Setup\",\n category: \"microsoft\",\n steps: [\n \"Uses same Microsoft OAuth credentials as Outlook\",\n \"Add Teams permissions: Chat.Read, Chat.ReadWrite, Channel.ReadBasic.All\",\n \"Add redirect URI: http://localhost:3000/api/auth/teams/callback\",\n ],\n link: \"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\",\n envVars: [\"MICROSOFT_CLIENT_ID\", \"MICROSOFT_CLIENT_SECRET\"],\n },\n onedrive: {\n title: \"Microsoft OneDrive Setup\",\n category: \"microsoft\",\n steps: [\n \"Uses same Microsoft OAuth credentials\",\n \"Add permissions: Files.Read, Files.ReadWrite\",\n \"Add redirect URI: http://localhost:3000/api/auth/onedrive/callback\",\n ],\n link: \"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\",\n envVars: [\"MICROSOFT_CLIENT_ID\", \"MICROSOFT_CLIENT_SECRET\"],\n },\n sharepoint: {\n title: \"Microsoft SharePoint Setup\",\n category: \"microsoft\",\n steps: [\n \"Uses same Microsoft OAuth credentials\",\n \"Add permissions: Sites.Read.All, Sites.ReadWrite.All\",\n \"Add redirect URI: http://localhost:3000/api/auth/sharepoint/callback\",\n ],\n link: \"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\",\n envVars: [\"MICROSOFT_CLIENT_ID\", \"MICROSOFT_CLIENT_SECRET\"],\n },\n\n jira: {\n title: \"Atlassian Jira Setup\",\n category: \"atlassian\",\n steps: [\n \"Go to Atlassian Developer Console\",\n \"Click Create > OAuth 2.0 integration\",\n \"Add Jira API scopes: read:jira-work, write:jira-work\",\n \"Set callback URL: http://localhost:3000/api/auth/jira/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developer.atlassian.com/console/myapps/\",\n envVars: [\"ATLASSIAN_CLIENT_ID\", \"ATLASSIAN_CLIENT_SECRET\"],\n },\n confluence: {\n title: \"Atlassian Confluence Setup\",\n category: \"atlassian\",\n steps: [\n \"Uses same Atlassian OAuth credentials as Jira\",\n \"Add Confluence scopes: read:confluence-content.all, write:confluence-content\",\n \"Add callback URL: http://localhost:3000/api/auth/confluence/callback\",\n ],\n link: \"https://developer.atlassian.com/console/myapps/\",\n envVars: [\"ATLASSIAN_CLIENT_ID\", \"ATLASSIAN_CLIENT_SECRET\"],\n },\n bitbucket: {\n title: \"Atlassian Bitbucket Setup\",\n category: \"atlassian\",\n steps: [\n \"Go to Bitbucket Settings > OAuth consumers\",\n \"Click Add consumer\",\n \"Set callback URL: http://localhost:3000/api/auth/bitbucket/callback\",\n \"Add permissions: repository:read, repository:write\",\n \"Copy Key and Secret to .env\",\n ],\n link: \"https://bitbucket.org/account/settings/app-passwords/\",\n envVars: [\"BITBUCKET_CLIENT_ID\", \"BITBUCKET_CLIENT_SECRET\"],\n },\n\n slack: {\n title: \"Slack App Setup\",\n category: \"communication\",\n steps: [\n \"Go to Slack API Apps page\",\n \"Click Create New App > From scratch\",\n \"Go to OAuth & Permissions\",\n \"Add scopes: channels:read, chat:write, users:read, channels:history\",\n \"Add redirect URL: http://localhost:3000/api/auth/slack/callback\",\n \"Install to Workspace\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://api.slack.com/apps\",\n envVars: [\"SLACK_CLIENT_ID\", \"SLACK_CLIENT_SECRET\"],\n },\n discord: {\n title: \"Discord App Setup\",\n category: \"communication\",\n steps: [\n \"Go to Discord Developer Portal\",\n \"Click New Application\",\n \"Go to OAuth2 section\",\n \"Add redirect: http://localhost:3000/api/auth/discord/callback\",\n \"Copy Client ID and Secret to .env\",\n \"Add bot permissions as needed\",\n ],\n link: \"https://discord.com/developers/applications\",\n envVars: [\"DISCORD_CLIENT_ID\", \"DISCORD_CLIENT_SECRET\"],\n },\n zoom: {\n title: \"Zoom App Setup\",\n category: \"communication\",\n steps: [\n \"Go to Zoom App Marketplace\",\n \"Click Develop > Build App\",\n \"Choose OAuth app type\",\n \"Add redirect URL: http://localhost:3000/api/auth/zoom/callback\",\n \"Add scopes: meeting:read, meeting:write, user:read\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://marketplace.zoom.us/develop/create\",\n envVars: [\"ZOOM_CLIENT_ID\", \"ZOOM_CLIENT_SECRET\"],\n },\n webex: {\n title: \"Webex Integration Setup\",\n category: \"communication\",\n steps: [\n \"Go to Webex Developer Portal\",\n \"Create a new integration\",\n \"Add redirect URI: http://localhost:3000/api/auth/webex/callback\",\n \"Select required scopes\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developer.webex.com/my-apps\",\n envVars: [\"WEBEX_CLIENT_ID\", \"WEBEX_CLIENT_SECRET\"],\n },\n twilio: {\n title: \"Twilio Setup\",\n category: \"communication\",\n steps: [\n \"Go to Twilio Console\",\n \"Copy Account SID and Auth Token\",\n \"Get a phone number for SMS\",\n \"Add credentials to .env\",\n ],\n link: \"https://console.twilio.com/\",\n envVars: [\"TWILIO_ACCOUNT_SID\", \"TWILIO_AUTH_TOKEN\", \"TWILIO_PHONE_NUMBER\"],\n },\n\n github: {\n title: \"GitHub OAuth App Setup\",\n category: \"development\",\n steps: [\n \"Go to GitHub Developer Settings\",\n \"Click OAuth Apps > New OAuth App\",\n \"Set Homepage URL: http://localhost:3000\",\n \"Set callback URL: http://localhost:3000/api/auth/github/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://github.com/settings/developers\",\n envVars: [\"GITHUB_CLIENT_ID\", \"GITHUB_CLIENT_SECRET\"],\n },\n gitlab: {\n title: \"GitLab OAuth Setup\",\n category: \"development\",\n steps: [\n \"Go to GitLab User Settings > Applications\",\n \"Create new application\",\n \"Add redirect URI: http://localhost:3000/api/auth/gitlab/callback\",\n \"Select scopes: api, read_user, read_repository\",\n \"Copy Application ID and Secret to .env\",\n ],\n link: \"https://gitlab.com/-/profile/applications\",\n envVars: [\"GITLAB_CLIENT_ID\", \"GITLAB_CLIENT_SECRET\"],\n },\n sentry: {\n title: \"Sentry Setup\",\n category: \"development\",\n steps: [\n \"Go to Sentry Settings > Developer Settings\",\n \"Create new integration\",\n \"Add redirect URL: http://localhost:3000/api/auth/sentry/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://sentry.io/settings/developer-settings/\",\n envVars: [\"SENTRY_CLIENT_ID\", \"SENTRY_CLIENT_SECRET\"],\n },\n posthog: {\n title: \"PostHog Setup\",\n category: \"development\",\n steps: [\"Go to PostHog Project Settings\", \"Copy your Project API Key\", \"Add to .env file\"],\n link: \"https://app.posthog.com/project/settings\",\n envVars: [\"POSTHOG_API_KEY\", \"POSTHOG_HOST\"],\n },\n mixpanel: {\n title: \"Mixpanel Setup\",\n category: \"development\",\n steps: [\n \"Go to Mixpanel Project Settings\",\n \"Copy your Project Token\",\n \"For API access, create a Service Account\",\n \"Add credentials to .env\",\n ],\n link: \"https://mixpanel.com/settings/project\",\n envVars: [\"MIXPANEL_TOKEN\", \"MIXPANEL_API_SECRET\"],\n },\n\n notion: {\n title: \"Notion Integration Setup\",\n category: \"productivity\",\n steps: [\n \"Go to Notion Integrations page\",\n \"Click New integration\",\n \"Name your integration and select workspace\",\n \"Copy the Internal Integration Token\",\n \"Share desired pages/databases with your integration\",\n \"Add token to .env\",\n ],\n link: \"https://www.notion.so/my-integrations\",\n envVars: [\"NOTION_API_KEY\"],\n },\n linear: {\n title: \"Linear OAuth Setup\",\n category: \"productivity\",\n steps: [\n \"Go to Linear Settings > API\",\n \"Create new OAuth application\",\n \"Add redirect URI: http://localhost:3000/api/auth/linear/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://linear.app/settings/api\",\n envVars: [\"LINEAR_CLIENT_ID\", \"LINEAR_CLIENT_SECRET\"],\n },\n asana: {\n title: \"Asana OAuth Setup\",\n category: \"productivity\",\n steps: [\n \"Go to Asana Developer Console\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/asana/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://app.asana.com/0/developer-console\",\n envVars: [\"ASANA_CLIENT_ID\", \"ASANA_CLIENT_SECRET\"],\n },\n trello: {\n title: \"Trello Power-Up Setup\",\n category: \"productivity\",\n steps: [\n \"Go to Trello Power-Ups Admin\",\n \"Create new Power-Up\",\n \"Add redirect URI: http://localhost:3000/api/auth/trello/callback\",\n \"Copy API Key and Secret to .env\",\n ],\n link: \"https://trello.com/power-ups/admin\",\n envVars: [\"TRELLO_API_KEY\", \"TRELLO_API_SECRET\"],\n },\n monday: {\n title: \"Monday.com App Setup\",\n category: \"productivity\",\n steps: [\n \"Go to monday.com Developers\",\n \"Create new app\",\n \"Add OAuth redirect: http://localhost:3000/api/auth/monday/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://monday.com/developers/apps\",\n envVars: [\"MONDAY_CLIENT_ID\", \"MONDAY_CLIENT_SECRET\"],\n },\n clickup: {\n title: \"ClickUp OAuth Setup\",\n category: \"productivity\",\n steps: [\n \"Go to ClickUp Settings > Apps\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/clickup/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://app.clickup.com/settings/apps\",\n envVars: [\"CLICKUP_CLIENT_ID\", \"CLICKUP_CLIENT_SECRET\"],\n },\n\n dropbox: {\n title: \"Dropbox App Setup\",\n category: \"storage\",\n steps: [\n \"Go to Dropbox App Console\",\n \"Create new app\",\n \"Choose Scoped access and Full Dropbox\",\n \"Add redirect URI: http://localhost:3000/api/auth/dropbox/callback\",\n \"Copy App Key and Secret to .env\",\n ],\n link: \"https://www.dropbox.com/developers/apps\",\n envVars: [\"DROPBOX_CLIENT_ID\", \"DROPBOX_CLIENT_SECRET\"],\n },\n box: {\n title: \"Box App Setup\",\n category: \"storage\",\n steps: [\n \"Go to Box Developer Console\",\n \"Create new app with OAuth 2.0\",\n \"Add redirect URI: http://localhost:3000/api/auth/box/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://app.box.com/developers/console\",\n envVars: [\"BOX_CLIENT_ID\", \"BOX_CLIENT_SECRET\"],\n },\n airtable: {\n title: \"Airtable OAuth Setup\",\n category: \"storage\",\n steps: [\n \"Go to Airtable Developer Hub\",\n \"Create new OAuth integration\",\n \"Add redirect URI: http://localhost:3000/api/auth/airtable/callback\",\n \"Select required scopes\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://airtable.com/create/oauth\",\n envVars: [\"AIRTABLE_CLIENT_ID\", \"AIRTABLE_CLIENT_SECRET\"],\n },\n\n supabase: {\n title: \"Supabase Setup\",\n category: \"infrastructure\",\n steps: [\n \"Go to Supabase Dashboard\",\n \"Create new project or select existing\",\n \"Go to Settings > API\",\n \"Copy Project URL and anon/service_role keys\",\n \"Add to .env file\",\n ],\n link: \"https://supabase.com/dashboard\",\n envVars: [\"SUPABASE_URL\", \"SUPABASE_ANON_KEY\", \"SUPABASE_SERVICE_ROLE_KEY\"],\n },\n neon: {\n title: \"Neon Database Setup\",\n category: \"infrastructure\",\n steps: [\n \"Go to Neon Console\",\n \"Create new project\",\n \"Copy connection string from Dashboard\",\n \"Add to .env file\",\n ],\n link: \"https://console.neon.tech/\",\n envVars: [\"DATABASE_URL\"],\n },\n snowflake: {\n title: \"Snowflake Setup\",\n category: \"infrastructure\",\n steps: [\n \"Go to Snowflake Console\",\n \"Create a service account or use existing credentials\",\n \"Note your account identifier, warehouse, database\",\n \"Add credentials to .env\",\n ],\n link: \"https://app.snowflake.com/\",\n envVars: [\"SNOWFLAKE_ACCOUNT\", \"SNOWFLAKE_USER\", \"SNOWFLAKE_PASSWORD\", \"SNOWFLAKE_WAREHOUSE\"],\n },\n aws: {\n title: \"AWS Setup\",\n category: \"infrastructure\",\n steps: [\n \"Go to AWS IAM Console\",\n \"Create new IAM user with programmatic access\",\n \"Attach required policies (S3, Lambda, DynamoDB)\",\n \"Copy Access Key ID and Secret\",\n \"Add to .env file\",\n ],\n link: \"https://console.aws.amazon.com/iam/\",\n envVars: [\"AWS_ACCESS_KEY_ID\", \"AWS_SECRET_ACCESS_KEY\", \"AWS_REGION\"],\n },\n\n salesforce: {\n title: \"Salesforce Connected App Setup\",\n category: \"sales\",\n steps: [\n \"Go to Salesforce Setup > App Manager\",\n \"Create new Connected App\",\n \"Enable OAuth Settings\",\n \"Add callback URL: http://localhost:3000/api/auth/salesforce/callback\",\n \"Select OAuth scopes: api, refresh_token\",\n \"Copy Consumer Key and Secret to .env\",\n ],\n link: \"https://login.salesforce.com/\",\n envVars: [\"SALESFORCE_CLIENT_ID\", \"SALESFORCE_CLIENT_SECRET\"],\n },\n hubspot: {\n title: \"HubSpot App Setup\",\n category: \"sales\",\n steps: [\n \"Go to HubSpot Developer Portal\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/hubspot/callback\",\n \"Select required scopes\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developers.hubspot.com/\",\n envVars: [\"HUBSPOT_CLIENT_ID\", \"HUBSPOT_CLIENT_SECRET\"],\n },\n pipedrive: {\n title: \"Pipedrive OAuth Setup\",\n category: \"sales\",\n steps: [\n \"Go to Pipedrive Developer Hub\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/pipedrive/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developers.pipedrive.com/\",\n envVars: [\"PIPEDRIVE_CLIENT_ID\", \"PIPEDRIVE_CLIENT_SECRET\"],\n },\n\n zendesk: {\n title: \"Zendesk OAuth Setup\",\n category: \"support\",\n steps: [\n \"Go to Zendesk Admin > API > OAuth Clients\",\n \"Add new OAuth client\",\n \"Set redirect URI: http://localhost:3000/api/auth/zendesk/callback\",\n \"Copy Client ID and Secret to .env\",\n \"Add your Zendesk subdomain\",\n ],\n link: \"https://support.zendesk.com/hc/en-us/articles/4408845965210\",\n envVars: [\"ZENDESK_CLIENT_ID\", \"ZENDESK_CLIENT_SECRET\", \"ZENDESK_SUBDOMAIN\"],\n },\n intercom: {\n title: \"Intercom OAuth Setup\",\n category: \"support\",\n steps: [\n \"Go to Intercom Developer Hub\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/intercom/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developers.intercom.com/\",\n envVars: [\"INTERCOM_CLIENT_ID\", \"INTERCOM_CLIENT_SECRET\"],\n },\n freshdesk: {\n title: \"Freshdesk OAuth Setup\",\n category: \"support\",\n steps: [\n \"Go to Freshdesk Admin > Apps > Custom Apps\",\n \"Create new OAuth application\",\n \"Add redirect URI: http://localhost:3000/api/auth/freshdesk/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developers.freshdesk.com/\",\n envVars: [\"FRESHDESK_CLIENT_ID\", \"FRESHDESK_CLIENT_SECRET\", \"FRESHDESK_DOMAIN\"],\n },\n servicenow: {\n title: \"ServiceNow OAuth Setup\",\n category: \"support\",\n steps: [\n \"Go to ServiceNow System OAuth > Application Registry\",\n \"Create OAuth API endpoint for external clients\",\n \"Add redirect URL: http://localhost:3000/api/auth/servicenow/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://docs.servicenow.com/\",\n envVars: [\"SERVICENOW_CLIENT_ID\", \"SERVICENOW_CLIENT_SECRET\", \"SERVICENOW_INSTANCE\"],\n },\n\n stripe: {\n title: \"Stripe Setup\",\n category: \"finance\",\n steps: [\n \"Go to Stripe Dashboard\",\n \"Go to Developers > API keys\",\n \"Copy Publishable and Secret keys\",\n \"For Connect, set up OAuth in Connect settings\",\n \"Add to .env file\",\n ],\n link: \"https://dashboard.stripe.com/apikeys\",\n envVars: [\"STRIPE_SECRET_KEY\", \"STRIPE_PUBLISHABLE_KEY\"],\n },\n quickbooks: {\n title: \"QuickBooks OAuth Setup\",\n category: \"finance\",\n steps: [\n \"Go to Intuit Developer Portal\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/quickbooks/callback\",\n \"Select Accounting scope\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developer.intuit.com/app/developer/dashboard\",\n envVars: [\"QUICKBOOKS_CLIENT_ID\", \"QUICKBOOKS_CLIENT_SECRET\"],\n },\n xero: {\n title: \"Xero OAuth Setup\",\n category: \"finance\",\n steps: [\n \"Go to Xero Developer Portal\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/xero/callback\",\n \"Select required scopes\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developer.xero.com/app/manage\",\n envVars: [\"XERO_CLIENT_ID\", \"XERO_CLIENT_SECRET\"],\n },\n\n mailchimp: {\n title: \"Mailchimp OAuth Setup\",\n category: \"marketing\",\n steps: [\n \"Go to Mailchimp Developer Portal\",\n \"Register new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/mailchimp/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://admin.mailchimp.com/account/oauth2/\",\n envVars: [\"MAILCHIMP_CLIENT_ID\", \"MAILCHIMP_CLIENT_SECRET\"],\n },\n shopify: {\n title: \"Shopify App Setup\",\n category: \"marketing\",\n steps: [\n \"Go to Shopify Partners Dashboard\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/shopify/callback\",\n \"Copy API Key and Secret to .env\",\n ],\n link: \"https://partners.shopify.com/\",\n envVars: [\"SHOPIFY_API_KEY\", \"SHOPIFY_API_SECRET\"],\n },\n twitter: {\n title: \"Twitter/X OAuth Setup\",\n category: \"marketing\",\n steps: [\n \"Go to Twitter Developer Portal\",\n \"Create new project and app\",\n \"Enable OAuth 2.0\",\n \"Add redirect URI: http://localhost:3000/api/auth/twitter/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developer.twitter.com/en/portal/dashboard\",\n envVars: [\"TWITTER_CLIENT_ID\", \"TWITTER_CLIENT_SECRET\"],\n },\n\n figma: {\n title: \"Figma OAuth Setup\",\n category: \"design\",\n steps: [\n \"Go to Figma Developer Settings\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/figma/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://www.figma.com/developers/apps\",\n envVars: [\"FIGMA_CLIENT_ID\", \"FIGMA_CLIENT_SECRET\"],\n },\n\n anthropic: {\n title: \"Anthropic API Setup\",\n category: \"ai\",\n steps: [\"Go to Anthropic Console\", \"Create new API key\", \"Copy API key to .env\"],\n link: \"https://console.anthropic.com/\",\n envVars: [\"ANTHROPIC_API_KEY\"],\n },\n};\n\nexport default function SetupPage(): React.JSX.Element {\n const [integrations, setIntegrations] = useState<Integration[]>([]);\n const [loading, setLoading] = useState(true);\n const [expandedGuide, setExpandedGuide] = useState<string | null>(null);\n const [envChecked, setEnvChecked] = useState(false);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [selectedCategory, setSelectedCategory] = useState<string | null>(null);\n const [tokenStorage, setTokenStorage] = useState<TokenStorageStatus | null>(null);\n\n useEffect(() => {\n void fetchStatus();\n void fetchTokenStorage();\n }, []);\n\n async function fetchStatus(): Promise<void> {\n try {\n const res = await fetch(\"/api/integrations/status\");\n if (!res.ok) {\n console.error(\"Failed to fetch integration status:\", res.status);\n setIntegrations([]);\n return;\n }\n\n const data = await res.json();\n setIntegrations(data.integrations ?? []);\n } catch (error) {\n console.error(\"Failed to fetch integration status:\", error);\n setIntegrations([]);\n } finally {\n setLoading(false);\n }\n }\n\n async function fetchTokenStorage(): Promise<void> {\n try {\n const res = await fetch(\"/api/integrations/token-storage\");\n if (!res.ok) {\n setTokenStorage({ mode: \"memory\", encrypted: false });\n return;\n }\n const data = await res.json();\n setTokenStorage(data);\n } catch {\n setTokenStorage({ mode: \"memory\", encrypted: false });\n }\n }\n\n const filteredIntegrations = useMemo(() => {\n const query = searchQuery.toLowerCase();\n\n return integrations.filter((integration) => {\n const guide = OAUTH_SETUP_GUIDES[integration.id];\n\n const matchesSearch =\n query === \"\" ||\n integration.name.toLowerCase().includes(query) ||\n integration.id.toLowerCase().includes(query);\n\n const matchesCategory =\n selectedCategory === null || guide?.category === selectedCategory;\n\n return matchesSearch && matchesCategory;\n });\n }, [integrations, searchQuery, selectedCategory]);\n\n const groupedIntegrations = useMemo(() => {\n const groups: Record<string, Integration[]> = {};\n\n for (const integration of filteredIntegrations) {\n const category = OAUTH_SETUP_GUIDES[integration.id]?.category ?? \"other\";\n (groups[category] ??= []).push(integration);\n }\n\n return groups;\n }, [filteredIntegrations]);\n\n const connectedCount = integrations.filter((i) => i.connected).length;\n const totalCount = integrations.length;\n const progress = totalCount > 0 ? (connectedCount / totalCount) * 100 : 0;\n\n const setupSteps: SetupStep[] = [\n {\n id: \"env\",\n title: \"Configure Environment Variables\",\n description: \"Add your OAuth credentials to the .env file\",\n completed: envChecked,\n action: () => setEnvChecked(true),\n },\n {\n id: \"oauth\",\n title: \"Create OAuth Apps\",\n description: \"Set up OAuth applications for each service\",\n completed: false,\n },\n {\n id: \"connect\",\n title: \"Connect Services\",\n description: \"Authorize your app to access each service\",\n completed: connectedCount === totalCount && totalCount > 0,\n },\n ];\n\n const tokenStorageContainerClassName = tokenStorage\n ? `rounded-2xl p-6 shadow-sm border mb-8 ${\n tokenStorage.mode === \"memory\"\n ? \"bg-amber-50 dark:bg-amber-900/20 border-amber-200 dark:border-amber-800\"\n : \"bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800\"\n }`\n : \"\";\n\n const tokenStorageIconWrapperClassName = tokenStorage\n ? `w-10 h-10 rounded-full flex items-center justify-center ${\n tokenStorage.mode === \"memory\"\n ? \"bg-amber-100 dark:bg-amber-900\"\n : \"bg-green-100 dark:bg-green-900\"\n }`\n : \"\";\n\n const tokenStorageTitleClassName = tokenStorage\n ? `font-semibold ${\n tokenStorage.mode === \"memory\"\n ? \"text-amber-800 dark:text-amber-200\"\n : \"text-green-800 dark:text-green-200\"\n }`\n : \"\";\n\n const tokenStorageTextClassName = tokenStorage\n ? `text-sm mt-1 ${\n tokenStorage.mode === \"memory\"\n ? \"text-amber-700 dark:text-amber-300\"\n : \"text-green-700 dark:text-green-300\"\n }`\n : \"\";\n\n return (\n <div className=\"min-h-screen bg-neutral-50 dark:bg-neutral-900\">\n <div className=\"max-w-4xl mx-auto px-4 py-12\">\n <div className=\"text-center mb-12\">\n <h1 className=\"text-4xl font-bold text-neutral-900 dark:text-white mb-4\">\n Setup Your AI Agent\n </h1>\n <p className=\"text-lg text-neutral-600 dark:text-neutral-400\">\n Connect your services to enable AI-powered automation\n </p>\n </div>\n\n <div className=\"bg-white dark:bg-neutral-800 rounded-2xl p-6 shadow-sm border border-neutral-200 dark:border-neutral-700 mb-8\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm font-medium text-neutral-600 dark:text-neutral-400\">\n Setup Progress\n </span>\n <span className=\"text-sm font-medium text-neutral-900 dark:text-white\">\n {connectedCount} / {totalCount} services connected\n </span>\n </div>\n <div className=\"w-full bg-neutral-200 dark:bg-neutral-700 rounded-full h-3\">\n <div\n className=\"bg-gradient-to-r from-green-500 to-emerald-500 h-3 rounded-full transition-all duration-500\"\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n\n {tokenStorage && (\n <div className={tokenStorageContainerClassName}>\n <div className=\"flex items-start gap-4\">\n <div className={tokenStorageIconWrapperClassName}>\n {tokenStorage.mode === \"memory\" ? (\n <svg\n className=\"w-5 h-5 text-amber-600 dark:text-amber-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n ) : (\n <svg\n className=\"w-5 h-5 text-green-600 dark:text-green-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z\"\n />\n </svg>\n )}\n </div>\n\n <div className=\"flex-1\">\n <h3 className={tokenStorageTitleClassName}>\n Token Storage:{\" \"}\n {tokenStorage.mode === \"memory\"\n ? \"Development Mode\"\n : `${tokenStorage.mode.charAt(0).toUpperCase()}${tokenStorage.mode.slice(\n 1,\n )} Storage`}\n </h3>\n\n <p className={tokenStorageTextClassName}>\n {tokenStorage.mode === \"memory\" ? (\n <>Tokens are stored in memory and will be lost on restart.</>\n ) : (\n <>Tokens are persisted to {tokenStorage.mode} storage.</>\n )}\n </p>\n\n <div className=\"mt-2 flex items-center gap-1.5 text-sm text-green-600 dark:text-green-400\">\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n <span>Encryption enabled {tokenStorage.autoGenerated && \"(auto-generated key)\"}</span>\n </div>\n\n {tokenStorage.mode === \"memory\" && (\n <div className=\"mt-4 pt-4 border-t border-amber-200 dark:border-amber-800\">\n <p className=\"text-sm font-medium text-amber-800 dark:text-amber-200 mb-3\">\n For production, add one of these to your{\" \"}\n <code className=\"px-1 py-0.5 bg-amber-100 dark:bg-amber-900 rounded text-xs\">\n .env\n </code>\n :\n </p>\n <div className=\"grid gap-2\">\n <a\n href=\"https://upstash.com/docs/redis/overall/getstarted\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center justify-between p-3 bg-white dark:bg-neutral-800 rounded-lg border border-green-200 dark:border-green-700 hover:border-green-400 dark:hover:border-green-500 transition-colors group\"\n >\n <div>\n <span className=\"font-medium text-neutral-900 dark:text-white\">\n Upstash\n </span>\n <span className=\"text-green-600 dark:text-green-400 text-xs ml-2 font-medium\">\n Recommended\n </span>\n <span className=\"text-neutral-500 dark:text-neutral-400 text-sm ml-2\">\n Serverless Redis, scales horizontally\n </span>\n </div>\n <code className=\"text-xs bg-neutral-100 dark:bg-neutral-700 px-2 py-1 rounded text-neutral-600 dark:text-neutral-300\">\n REDIS_URL\n </code>\n </a>\n\n <a\n href=\"https://docs.turso.tech/quickstart\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center justify-between p-3 bg-white dark:bg-neutral-800 rounded-lg border border-amber-200 dark:border-amber-700 hover:border-amber-400 dark:hover:border-amber-500 transition-colors group\"\n >\n <div>\n <span className=\"font-medium text-neutral-900 dark:text-white\">\n Turso / libSQL\n </span>\n <span className=\"text-neutral-500 dark:text-neutral-400 text-sm ml-2\">\n Edge SQLite, fast reads globally\n </span>\n </div>\n <code className=\"text-xs bg-neutral-100 dark:bg-neutral-700 px-2 py-1 rounded text-neutral-600 dark:text-neutral-300\">\n DATABASE_URL\n </code>\n </a>\n\n <a\n href=\"https://vercel.com/docs/storage/vercel-kv/quickstart\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center justify-between p-3 bg-white dark:bg-neutral-800 rounded-lg border border-amber-200 dark:border-amber-700 hover:border-amber-400 dark:hover:border-amber-500 transition-colors group\"\n >\n <div>\n <span className=\"font-medium text-neutral-900 dark:text-white\">\n Vercel KV\n </span>\n <span className=\"text-neutral-500 dark:text-neutral-400 text-sm ml-2\">\n Built-in if using Vercel\n </span>\n </div>\n <code className=\"text-xs bg-neutral-100 dark:bg-neutral-700 px-2 py-1 rounded text-neutral-600 dark:text-neutral-300\">\n KV_REST_API_URL\n </code>\n </a>\n\n <a\n href=\"https://neon.tech/docs/get-started-with-neon/connect-neon\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center justify-between p-3 bg-white dark:bg-neutral-800 rounded-lg border border-amber-200 dark:border-amber-700 hover:border-amber-400 dark:hover:border-amber-500 transition-colors group\"\n >\n <div>\n <span className=\"font-medium text-neutral-900 dark:text-white\">Neon</span>\n <span className=\"text-neutral-500 dark:text-neutral-400 text-sm ml-2\">\n Serverless Postgres\n </span>\n </div>\n <code className=\"text-xs bg-neutral-100 dark:bg-neutral-700 px-2 py-1 rounded text-neutral-600 dark:text-neutral-300\">\n DATABASE_URL\n </code>\n </a>\n\n <a\n href=\"https://www.sqlite.org/index.html\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center justify-between p-3 bg-white dark:bg-neutral-800 rounded-lg border border-amber-200 dark:border-amber-700 hover:border-amber-400 dark:hover:border-amber-500 transition-colors group\"\n >\n <div>\n <span className=\"font-medium text-neutral-900 dark:text-white\">\n SQLite\n </span>\n <span className=\"text-neutral-500 dark:text-neutral-400 text-sm ml-2\">\n Local file, single instance only\n </span>\n </div>\n <code className=\"text-xs bg-neutral-100 dark:bg-neutral-700 px-2 py-1 rounded text-neutral-600 dark:text-neutral-300\">\n DATABASE_URL=file:./data.db\n </code>\n </a>\n </div>\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n\n <div className=\"bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 mb-8 overflow-hidden\">\n <div className=\"p-6 border-b border-neutral-200 dark:border-neutral-700\">\n <h2 className=\"text-xl font-semibold text-neutral-900 dark:text-white\">\n Quick Start Guide\n </h2>\n </div>\n <div className=\"divide-y divide-neutral-200 dark:divide-neutral-700\">\n {setupSteps.map((step, index) => (\n <div key={step.id} className=\"p-6 flex items-start gap-4\">\n <div\n className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${\n step.completed\n ? \"bg-green-500 text-white\"\n : \"bg-neutral-200 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-400\"\n }`}\n >\n {step.completed ? (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n ) : (\n <span className=\"font-semibold\">{index + 1}</span>\n )}\n </div>\n <div className=\"flex-1\">\n <h3 className=\"font-semibold text-neutral-900 dark:text-white\">{step.title}</h3>\n <p className=\"text-sm text-neutral-600 dark:text-neutral-400 mt-1\">\n {step.description}\n </p>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n <div className=\"bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 overflow-hidden\">\n <div className=\"p-6 border-b border-neutral-200 dark:border-neutral-700\">\n <h2 className=\"text-xl font-semibold text-neutral-900 dark:text-white\">\n Service Connections\n </h2>\n <p className=\"text-sm text-neutral-600 dark:text-neutral-400 mt-1\">\n Click on a service to see setup instructions or connect\n </p>\n\n <div className=\"mt-4\">\n <input\n type=\"text\"\n placeholder=\"Search services...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full px-4 py-2 bg-neutral-100 dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 rounded-xl text-neutral-900 dark:text-white placeholder-neutral-500 focus:outline-none focus:ring-2 focus:ring-blue-500\"\n />\n </div>\n\n <div className=\"mt-4 flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => setSelectedCategory(null)}\n className={`px-3 py-1.5 text-sm font-medium rounded-lg transition-colors ${\n selectedCategory === null\n ? \"bg-neutral-900 dark:bg-white text-white dark:text-neutral-900\"\n : \"bg-neutral-100 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-300 hover:bg-neutral-200 dark:hover:bg-neutral-600\"\n }`}\n >\n All\n </button>\n\n {CATEGORIES.map((category) => (\n <button\n key={category.id}\n type=\"button\"\n onClick={() =>\n setSelectedCategory(selectedCategory === category.id ? null : category.id)\n }\n className={`px-3 py-1.5 text-sm font-medium rounded-lg transition-colors ${\n selectedCategory === category.id\n ? \"bg-neutral-900 dark:bg-white text-white dark:text-neutral-900\"\n : \"bg-neutral-100 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-300 hover:bg-neutral-200 dark:hover:bg-neutral-600\"\n }`}\n >\n {category.name}\n </button>\n ))}\n </div>\n </div>\n\n {loading ? (\n <div className=\"p-12 text-center text-neutral-500\">Loading...</div>\n ) : filteredIntegrations.length === 0 ? (\n <div className=\"p-12 text-center text-neutral-500\">\n No services found matching your search\n </div>\n ) : (\n <div>\n {CATEGORIES.filter((cat) => groupedIntegrations[cat.id]?.length > 0).map(\n (category) => (\n <div key={category.id}>\n <div className=\"px-6 py-3 bg-neutral-50 dark:bg-neutral-900 border-b border-neutral-200 dark:border-neutral-700\">\n <h3 className=\"text-sm font-semibold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider\">\n {category.name}\n </h3>\n </div>\n\n <div className=\"divide-y divide-neutral-200 dark:divide-neutral-700\">\n {groupedIntegrations[category.id]?.map((integration) => {\n const guide = OAUTH_SETUP_GUIDES[integration.id];\n const isExpanded = expandedGuide === integration.id;\n\n return (\n <div key={integration.id}>\n <div className=\"p-6 flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <div className=\"w-12 h-12 bg-neutral-100 dark:bg-neutral-700 rounded-xl flex items-center justify-center\">\n <ServiceIcon name={integration.icon} />\n </div>\n <div>\n <h3 className=\"font-semibold text-neutral-900 dark:text-white\">\n {integration.name}\n </h3>\n <p\n className={`text-sm ${\n integration.connected\n ? \"text-green-600 dark:text-green-400\"\n : \"text-neutral-500\"\n }`}\n >\n {integration.connected ? \"Connected\" : \"Not connected\"}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-3\">\n {guide && (\n <button\n type=\"button\"\n onClick={() => setExpandedGuide(isExpanded ? null : integration.id)}\n className=\"px-4 py-2 text-sm font-medium text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white\"\n >\n {isExpanded ? \"Hide Guide\" : \"Setup Guide\"}\n </button>\n )}\n\n {integration.connected ? (\n <span className=\"inline-flex items-center gap-1.5 px-4 py-2 bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 rounded-xl text-sm font-medium\">\n <span className=\"w-2 h-2 bg-green-500 rounded-full\" />\n Connected\n </span>\n ) : (\n <a\n href={integration.connectUrl}\n className=\"px-4 py-2 bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 rounded-xl text-sm font-medium hover:opacity-90 transition-opacity\"\n >\n Connect\n </a>\n )}\n </div>\n </div>\n\n {isExpanded && guide && (\n <div className=\"px-6 pb-6\">\n <div className=\"bg-neutral-50 dark:bg-neutral-900 rounded-xl p-6 border border-neutral-200 dark:border-neutral-700\">\n <h4 className=\"font-semibold text-neutral-900 dark:text-white mb-4\">\n {guide.title}\n </h4>\n\n <ol className=\"space-y-3 mb-6\">\n {guide.steps.map((step, i) => (\n <li key={i} className=\"flex items-start gap-3\">\n <span className=\"w-6 h-6 bg-neutral-200 dark:bg-neutral-700 rounded-full flex items-center justify-center text-sm font-medium text-neutral-600 dark:text-neutral-400 flex-shrink-0\">\n {i + 1}\n </span>\n <span className=\"text-neutral-700 dark:text-neutral-300\">\n {step}\n </span>\n </li>\n ))}\n </ol>\n\n <div className=\"mb-4 p-4 bg-neutral-100 dark:bg-neutral-800 rounded-lg\">\n <h5 className=\"text-sm font-semibold text-neutral-700 dark:text-neutral-300 mb-2\">\n Required Environment Variables:\n </h5>\n <pre className=\"text-sm text-neutral-600 dark:text-neutral-400 font-mono whitespace-pre-wrap\">\n {guide.envVars.map((v) => `${v}=your_value`).join(\"\\n\")}\n </pre>\n </div>\n\n <a\n href={guide.link}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-2 text-blue-600 dark:text-blue-400 text-sm font-medium hover:underline\"\n >\n Open Developer Console\n <svg\n className=\"w-4 h-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14\"\n />\n </svg>\n </a>\n </div>\n </div>\n )}\n </div>\n );\n })}\n </div>\n </div>\n ),\n )}\n </div>\n )}\n </div>\n\n {connectedCount === totalCount && totalCount > 0 && (\n <div className=\"mt-8 bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20 rounded-2xl p-6 border border-green-200 dark:border-green-800 text-center\">\n <div className=\"text-4xl mb-4\">🎉</div>\n <h3 className=\"text-xl font-semibold text-green-800 dark:text-green-200 mb-2\">\n All Services Connected!\n </h3>\n <p className=\"text-green-700 dark:text-green-300 mb-4\">\n Your AI agent is ready to use. Start chatting to automate your workflows.\n </p>\n <a\n href=\"/\"\n className=\"inline-flex items-center gap-2 px-6 py-3 bg-green-600 text-white rounded-xl font-medium hover:bg-green-700 transition-colors\"\n >\n Start Using Your Agent\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 7l5 5m0 0l-5 5m5-5H6\"\n />\n </svg>\n </a>\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction ServiceIcon({ name }: { name: string }): React.JSX.Element {\n const iconMap: Record<string, React.JSX.Element> = {\n mail: (\n <svg className=\"w-6 h-6 text-red-500\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n d=\"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n fill=\"none\"\n />\n </svg>\n ),\n slack: (\n <svg className=\"w-6 h-6\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313z\"\n fill=\"#E01E5A\"\n />\n <path\n d=\"M8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312z\"\n fill=\"#36C5F0\"\n />\n <path\n d=\"M18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312z\"\n fill=\"#2EB67D\"\n />\n <path\n d=\"M15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z\"\n fill=\"#ECB22E\"\n />\n </svg>\n ),\n calendar: (\n <svg\n className=\"w-6 h-6 text-blue-500\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\"\n />\n </svg>\n ),\n github: (\n <svg className=\"w-6 h-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n fillRule=\"evenodd\"\n d=\"M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z\"\n clipRule=\"evenodd\"\n />\n </svg>\n ),\n jira: (\n <svg className=\"w-6 h-6\" viewBox=\"0 0 24 24\">\n <defs>\n <linearGradient id=\"jira-gradient\" x1=\"98.031%\" x2=\"58.888%\" y1=\".161%\" y2=\"40.766%\">\n <stop offset=\"0%\" stopColor=\"#0052CC\" />\n <stop offset=\"100%\" stopColor=\"#2684FF\" />\n </linearGradient>\n </defs>\n <path\n fill=\"url(#jira-gradient)\"\n d=\"M11.571 11.513H0a5.218 5.218 0 005.232 5.215h2.13v2.057A5.215 5.215 0 0012.575 24V12.518a1.005 1.005 0 00-1.005-1.005z\"\n />\n <path\n fill=\"#2684FF\"\n d=\"M17.151 5.97H5.58a5.215 5.215 0 005.215 5.214h2.129v2.058a5.218 5.218 0 005.232 5.215V6.975a1.005 1.005 0 00-1.005-1.005z\"\n />\n <path\n fill=\"#2684FF\"\n d=\"M22.723.426H11.152a5.215 5.215 0 005.215 5.215h2.129v2.057a5.218 5.218 0 005.232 5.215V1.431a1.005 1.005 0 00-1.005-1.005z\"\n />\n </svg>\n ),\n notion: (\n <svg className=\"w-6 h-6\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.98-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466l1.823 1.447zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.84-.046.933-.56.933-1.167V6.354c0-.606-.233-.933-.746-.886l-15.177.887c-.56.046-.747.326-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.746 0-.933-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.14c-.093-.514.28-.886.747-.933l3.222-.186zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.933.653.933 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.448-1.632z\" />\n </svg>\n ),\n default: (\n <svg className=\"w-6 h-6 text-neutral-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 10V3L4 14h7v7l9-11h-7z\"\n />\n </svg>\n ),\n };\n\n return iconMap[name] ?? iconMap.default;\n}\n",
|
|
321
|
+
"app/components/ServiceConnections.tsx": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\ninterface Service {\n id: string;\n name: string;\n connected: boolean;\n authUrl: string;\n}\n\ninterface ServiceConnectionsProps {\n services: Array<{\n id: string;\n name: string;\n authUrl: string;\n }>;\n className?: string;\n}\n\nfunction useIntegrationStatus(): { status: Record<string, boolean>; loading: boolean } {\n const [status, setStatus] = useState<Record<string, boolean>>({});\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n async function checkStatus(): Promise<void> {\n try {\n const res = await fetch(\"/api/integrations/status\");\n if (!res.ok) return;\n\n const data = await res.json();\n const integrations = data?.integrations ?? [];\n\n const statusMap: Record<string, boolean> = {};\n for (const integration of integrations) {\n statusMap[integration.id] = integration.connected;\n }\n\n setStatus(statusMap);\n } catch (error) {\n console.error(\"Failed to check service status:\", err);\n } finally {\n setLoading(false);\n }\n }\n\n checkStatus();\n }, []);\n\n return { status, loading };\n}\n\nexport function ServiceConnections({\n services,\n className = \"\",\n}: ServiceConnectionsProps): React.ReactElement {\n const { status, loading } = useIntegrationStatus();\n\n const servicesWithStatus: Service[] = services.map((s) => ({\n ...s,\n connected: status[s.id] ?? false,\n }));\n\n const connectedCount = servicesWithStatus.filter((s) => s.connected).length;\n\n if (loading) {\n return (\n <div className={`flex items-center gap-2 ${className}`}>\n <div className=\"animate-pulse h-6 w-32 bg-neutral-200 dark:bg-neutral-700 rounded\" />\n </div>\n );\n }\n\n return (\n <div className={`flex items-center gap-2 ${className}`}>\n {servicesWithStatus.map((service) => (\n <ServiceBadge key={service.id} service={service} />\n ))}\n {connectedCount < services.length && (\n <span className=\"text-xs text-neutral-500 dark:text-neutral-400 ml-1\">\n {connectedCount}/{services.length} connected\n </span>\n )}\n </div>\n );\n}\n\nfunction ServiceBadge({ service }: { service: Service }): React.ReactElement {\n if (service.connected) {\n return (\n <span\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400\"\n title={`${service.name} connected`}\n >\n <span className=\"w-1.5 h-1.5 rounded-full bg-green-500\" />\n {service.name}\n </span>\n );\n }\n\n function handleConnect(): void {\n globalThis.location.href = service.authUrl;\n }\n\n return (\n <button\n type=\"button\"\n onClick={handleConnect}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-neutral-100 text-neutral-600 hover:bg-neutral-200 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700 transition-colors\"\n title={`Connect ${service.name}`}\n >\n <span className=\"w-1.5 h-1.5 rounded-full bg-neutral-400\" />\n {service.name}\n </button>\n );\n}\n\nexport function ServiceConnectionsCard({\n services,\n className = \"\",\n}: ServiceConnectionsProps): React.ReactElement | null {\n const { status, loading } = useIntegrationStatus();\n\n const servicesWithStatus: Service[] = services.map((s) => ({\n ...s,\n connected: status[s.id] ?? false,\n }));\n\n const disconnectedServices = servicesWithStatus.filter((s) => !s.connected);\n\n if (loading || disconnectedServices.length === 0) return null;\n\n return (\n <div\n className={`rounded-lg border border-amber-200 dark:border-amber-900/50 bg-amber-50 dark:bg-amber-900/20 p-4 ${className}`}\n >\n <h3 className=\"font-medium text-amber-900 dark:text-amber-200 mb-2\">\n Connect your services\n </h3>\n <p className=\"text-sm text-amber-700 dark:text-amber-300/80 mb-3\">\n Connect the following services to unlock all features:\n </p>\n <div className=\"flex flex-wrap gap-2\">\n {disconnectedServices.map((service) => (\n <a\n key={service.id}\n href={service.authUrl}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium bg-amber-100 text-amber-800 hover:bg-amber-200 dark:bg-amber-900/40 dark:text-amber-200 dark:hover:bg-amber-900/60 transition-colors\"\n >\n Connect {service.name}\n </a>\n ))}\n </div>\n </div>\n );\n}\n",
|
|
322
|
+
"app/page.tsx": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport { Chat } from 'veryfront/components/ai'\nimport { useChat } from 'veryfront/agent/react'\n\ninterface Integration {\n id: string\n name: string\n connected: boolean\n connectUrl: string\n}\n\nexport default function ChatPage(): React.ReactElement {\n const chat = useChat({ api: '/api/chat' })\n\n return (\n <div className=\"flex flex-col h-screen bg-white dark:bg-neutral-900\">\n <header className=\"sticky top-0 z-10 flex-shrink-0 border-b border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900\">\n <div className=\"px-4 py-3 flex items-center justify-between\">\n <h1 className=\"font-medium text-neutral-900 dark:text-white\">AI Assistant</h1>\n <div className=\"flex items-center gap-4\">\n <IntegrationStatus />\n <a\n href=\"/setup\"\n className=\"text-sm text-neutral-500 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200\"\n >\n Setup\n </a>\n </div>\n </div>\n </header>\n\n <Chat {...chat} className=\"flex-1 min-h-0\" placeholder=\"Message\" />\n </div>\n )\n}\n\nfunction IntegrationStatus(): React.ReactElement | null {\n return <ServiceStatusFromAPI />\n}\n\nfunction ServiceStatusFromAPI(): React.ReactElement | null {\n const [integrations, setIntegrations] = useState<Integration[]>([])\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n async function fetchStatus(): Promise<void> {\n try {\n const res = await fetch('/api/integrations/status')\n if (!res.ok) return\n\n const data = await res.json()\n setIntegrations(data.integrations ?? [])\n } catch (error) {\n console.error('Failed to fetch integration status:', error)\n } finally {\n setLoading(false)\n }\n }\n\n fetchStatus()\n }, [])\n\n if (loading) {\n return (\n <div className=\"flex items-center gap-2\">\n <div className=\"animate-pulse h-6 w-24 bg-neutral-200 dark:bg-neutral-700 rounded-full\" />\n </div>\n )\n }\n\n if (integrations.length === 0) return null\n\n const connected = integrations.filter(integration => integration.connected)\n const disconnected = integrations.filter(integration => !integration.connected)\n\n return (\n <div className=\"flex items-center gap-2\">\n {connected.map(service => (\n <span\n key={service.id}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400\"\n title={`${service.name} connected`}\n >\n <span className=\"w-1.5 h-1.5 rounded-full bg-green-500\" />\n {service.name}\n </span>\n ))}\n\n {disconnected.map(service => (\n <a\n key={service.id}\n href={service.connectUrl}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-neutral-100 text-neutral-600 hover:bg-neutral-200 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700 transition-colors\"\n title={`Connect ${service.name}`}\n >\n <span className=\"w-1.5 h-1.5 rounded-full bg-neutral-400\" />\n {service.name}\n </a>\n ))}\n\n {disconnected.length > 0 && (\n <span className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {connected.length}/{integrations.length}\n </span>\n )}\n </div>\n )\n}\n"
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
"integration:outlook": {
|
|
326
|
+
"files": {
|
|
327
|
+
"lib/outlook-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst GRAPH_BASE_URL = \"https://graph.microsoft.com/v1.0\";\n\ninterface GraphResponse<T> {\n value?: T[];\n \"@odata.nextLink\"?: string;\n}\n\nexport interface OutlookMessage {\n id: string;\n subject: string;\n bodyPreview: string;\n body: {\n contentType: \"text\" | \"html\";\n content: string;\n };\n from: {\n emailAddress: {\n name: string;\n address: string;\n };\n };\n toRecipients: Array<{\n emailAddress: {\n name: string;\n address: string;\n };\n }>;\n ccRecipients?: Array<{\n emailAddress: {\n name: string;\n address: string;\n };\n }>;\n receivedDateTime: string;\n sentDateTime: string;\n isRead: boolean;\n hasAttachments: boolean;\n importance: \"low\" | \"normal\" | \"high\";\n conversationId: string;\n webLink: string;\n}\n\nexport interface OutlookFolder {\n id: string;\n displayName: string;\n parentFolderId: string;\n childFolderCount: number;\n unreadItemCount: number;\n totalItemCount: number;\n}\n\nexport interface SendEmailOptions {\n to: string[];\n subject: string;\n body: string;\n cc?: string[];\n bcc?: string[];\n importance?: \"low\" | \"normal\" | \"high\";\n bodyType?: \"text\" | \"html\";\n}\n\nasync function graphFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Microsoft. Please connect your account.\");\n }\n\n const response = await fetch(`${GRAPH_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(\n `Microsoft Graph API error: ${response.status} ${error.error?.message ?? response.statusText}`,\n );\n }\n\n return response.json();\n}\n\nexport async function listEmails(options?: {\n folderId?: string;\n top?: number;\n skip?: number;\n filter?: string;\n orderBy?: string;\n}): Promise<OutlookMessage[]> {\n const params = new URLSearchParams();\n\n if (options?.top) params.set(\"$top\", options.top.toString());\n if (options?.skip) params.set(\"$skip\", options.skip.toString());\n if (options?.filter) params.set(\"$filter\", options.filter);\n if (options?.orderBy) params.set(\"$orderby\", options.orderBy);\n\n const folderPath = options?.folderId\n ? `/mailFolders/${options.folderId}/messages`\n : \"/messages\";\n\n const queryString = params.toString();\n const endpoint = queryString ? `${folderPath}?${queryString}` : folderPath;\n\n const response = await graphFetch<GraphResponse<OutlookMessage>>(endpoint);\n return response.value ?? [];\n}\n\nexport function getEmail(messageId: string): Promise<OutlookMessage> {\n return graphFetch<OutlookMessage>(`/messages/${messageId}`);\n}\n\nexport async function sendEmail(options: SendEmailOptions): Promise<void> {\n const message = {\n subject: options.subject,\n body: {\n contentType: options.bodyType ?? \"text\",\n content: options.body,\n },\n toRecipients: options.to.map((email) => ({\n emailAddress: { address: email },\n })),\n ccRecipients: options.cc?.map((email) => ({\n emailAddress: { address: email },\n })),\n bccRecipients: options.bcc?.map((email) => ({\n emailAddress: { address: email },\n })),\n importance: options.importance ?? \"normal\",\n };\n\n await graphFetch(\"/sendMail\", {\n method: \"POST\",\n body: JSON.stringify({ message }),\n });\n}\n\nexport async function searchEmails(options: {\n query: string;\n top?: number;\n skip?: number;\n}): Promise<OutlookMessage[]> {\n const params = new URLSearchParams({\n $search: `\"${options.query}\"`,\n });\n\n if (options.top) params.set(\"$top\", options.top.toString());\n if (options.skip) params.set(\"$skip\", options.skip.toString());\n\n const response = await graphFetch<GraphResponse<OutlookMessage>>(\n `/messages?${params.toString()}`,\n );\n return response.value ?? [];\n}\n\nexport async function listFolders(): Promise<OutlookFolder[]> {\n const response = await graphFetch<GraphResponse<OutlookFolder>>(\"/mailFolders\");\n return response.value ?? [];\n}\n\nasync function setReadState(messageId: string, isRead: boolean): Promise<void> {\n await graphFetch(`/messages/${messageId}`, {\n method: \"PATCH\",\n body: JSON.stringify({ isRead }),\n });\n}\n\nexport async function markAsRead(messageId: string): Promise<void> {\n await setReadState(messageId, true);\n}\n\nexport async function markAsUnread(messageId: string): Promise<void> {\n await setReadState(messageId, false);\n}\n\nexport async function deleteEmail(messageId: string): Promise<void> {\n await graphFetch(`/messages/${messageId}`, { method: \"DELETE\" });\n}\n\nexport async function moveEmail(messageId: string, destinationFolderId: string): Promise<void> {\n await graphFetch(`/messages/${messageId}/move`, {\n method: \"POST\",\n body: JSON.stringify({ destinationId: destinationFolderId }),\n });\n}\n\nexport function formatEmail(message: OutlookMessage): string {\n const from = message.from.emailAddress.name || message.from.emailAddress.address;\n const to = message.toRecipients.map((r) => r.emailAddress.address).join(\", \");\n const date = new Date(message.receivedDateTime).toLocaleString();\n const read = message.isRead ? \"Yes\" : \"No\";\n\n return `From: ${from}\nTo: ${to}\nSubject: ${message.subject}\nDate: ${date}\nRead: ${read}\n\n${message.bodyPreview}`;\n}\n",
|
|
328
|
+
"tools/list-emails.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listEmails } from \"../../lib/outlook-client.ts\";\n\nexport default tool({\n id: \"list-emails\",\n description:\n \"List recent emails from inbox or a specific folder. Returns email metadata including subject, sender, date, and preview.\",\n inputSchema: z.object({\n folderId: z\n .string()\n .optional()\n .describe(\"Folder ID to list emails from (default: inbox)\"),\n limit: z\n .number()\n .min(1)\n .max(50)\n .default(10)\n .describe(\"Maximum number of emails to return\"),\n unreadOnly: z.boolean().default(false).describe(\"Only return unread emails\"),\n orderBy: z\n .enum([\"receivedDateTime desc\", \"receivedDateTime asc\", \"subject\"])\n .default(\"receivedDateTime desc\")\n .describe(\"Sort order for emails\"),\n }),\n async execute({ folderId, limit, unreadOnly, orderBy }) {\n const messages = await listEmails({\n folderId,\n top: limit,\n filter: unreadOnly ? \"isRead eq false\" : undefined,\n orderBy,\n });\n\n return messages.map((msg) => ({\n id: msg.id,\n subject: msg.subject,\n from: {\n name: msg.from.emailAddress.name,\n email: msg.from.emailAddress.address,\n },\n to: msg.toRecipients.map((r) => ({\n name: r.emailAddress.name,\n email: r.emailAddress.address,\n })),\n preview: msg.bodyPreview,\n receivedAt: msg.receivedDateTime,\n isRead: msg.isRead,\n hasAttachments: msg.hasAttachments,\n importance: msg.importance,\n webLink: msg.webLink,\n }));\n },\n});\n",
|
|
329
|
+
"tools/list-folders.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listFolders } from \"../../lib/outlook-client.ts\";\n\nexport default tool({\n id: \"list-folders\",\n description:\n \"List all mail folders in the mailbox, including inbox, sent items, drafts, and custom folders.\",\n inputSchema: z.object({}),\n async execute() {\n const folders = await listFolders();\n\n return folders.map(\n ({\n id,\n displayName,\n parentFolderId,\n childFolderCount,\n unreadItemCount,\n totalItemCount,\n }) => ({\n id,\n name: displayName,\n parentFolderId,\n childFolderCount,\n unreadItemCount,\n totalItemCount,\n }),\n );\n },\n});\n",
|
|
330
|
+
"tools/send-email.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { sendEmail } from \"../../lib/outlook-client.ts\";\n\nexport default tool({\n id: \"send-email\",\n description:\n \"Send a new email message. Supports multiple recipients, CC, BCC, and importance levels.\",\n inputSchema: z.object({\n to: z.array(z.string().email()).min(1).describe(\"Email addresses of recipients\"),\n subject: z.string().min(1).describe(\"Email subject line\"),\n body: z.string().min(1).describe(\"Email body content\"),\n cc: z.array(z.string().email()).optional().describe(\"Email addresses to CC\"),\n bcc: z.array(z.string().email()).optional().describe(\"Email addresses to BCC\"),\n importance: z\n .enum([\"low\", \"normal\", \"high\"])\n .default(\"normal\")\n .describe(\"Email importance level\"),\n bodyType: z\n .enum([\"text\", \"html\"])\n .default(\"text\")\n .describe(\"Body content type (text or html)\"),\n }),\n async execute({ to, subject, body, cc, bcc, importance, bodyType }) {\n await sendEmail({ to, subject, body, cc, bcc, importance, bodyType });\n\n return {\n success: true,\n message: `Email sent successfully to ${to.join(\", \")}`,\n recipients: { to, cc, bcc },\n };\n },\n});\n",
|
|
331
|
+
"tools/search-emails.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { searchEmails } from \"../../lib/outlook-client.ts\";\n\nexport default tool({\n id: \"search-emails\",\n description:\n \"Search emails by query string. Searches across subject, body, sender, and recipients. Supports advanced search syntax.\",\n inputSchema: z.object({\n query: z\n .string()\n .min(1)\n .describe(\"Search query (searches subject, body, from, to fields)\"),\n limit: z\n .number()\n .min(1)\n .max(50)\n .default(10)\n .describe(\"Maximum number of results to return\"),\n }),\n async execute({ query, limit }) {\n const messages = await searchEmails({ query, top: limit });\n\n return {\n totalResults: messages.length,\n emails: messages.map((msg) => ({\n id: msg.id,\n subject: msg.subject,\n from: {\n name: msg.from.emailAddress.name,\n email: msg.from.emailAddress.address,\n },\n to: msg.toRecipients.map((r) => ({\n name: r.emailAddress.name,\n email: r.emailAddress.address,\n })),\n preview: msg.bodyPreview,\n receivedAt: msg.receivedDateTime,\n isRead: msg.isRead,\n hasAttachments: msg.hasAttachments,\n importance: msg.importance,\n webLink: msg.webLink,\n })),\n };\n },\n});\n",
|
|
332
|
+
"tools/get-email.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getEmail } from \"../../lib/outlook-client.ts\";\n\nexport default tool({\n id: \"get-email\",\n description:\n \"Get detailed information about a specific email, including full body content, recipients, and metadata.\",\n inputSchema: z.object({\n messageId: z.string().describe(\"The ID of the email message to retrieve\"),\n includeBody: z\n .boolean()\n .default(true)\n .describe(\"Include full email body content\"),\n }),\n async execute({ messageId, includeBody }) {\n const message = await getEmail(messageId);\n\n const body = includeBody\n ? {\n contentType: message.body.contentType,\n content: message.body.content,\n }\n : undefined;\n\n return {\n id: message.id,\n subject: message.subject,\n from: {\n name: message.from.emailAddress.name,\n email: message.from.emailAddress.address,\n },\n to: message.toRecipients.map((r) => ({\n name: r.emailAddress.name,\n email: r.emailAddress.address,\n })),\n cc: message.ccRecipients?.map((r) => ({\n name: r.emailAddress.name,\n email: r.emailAddress.address,\n })),\n body,\n bodyPreview: message.bodyPreview,\n receivedAt: message.receivedDateTime,\n sentAt: message.sentDateTime,\n isRead: message.isRead,\n hasAttachments: message.hasAttachments,\n importance: message.importance,\n conversationId: message.conversationId,\n webLink: message.webLink,\n };\n },\n});\n",
|
|
333
|
+
"app/api/auth/outlook/route.ts": "import { createOAuthInitHandler, memoryTokenStore, outlookConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(outlookConfig, { tokenStore: memoryTokenStore });\n",
|
|
334
|
+
"app/api/auth/outlook/callback/route.ts": "import { createOAuthCallbackHandler, memoryTokenStore, outlookConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(outlookConfig, { tokenStore: hybridTokenStore });\n",
|
|
335
|
+
".env.example": "# Microsoft Outlook Integration\n# Get these from: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\n\n# Your Microsoft Azure App Client ID (Application ID)\nMICROSOFT_CLIENT_ID=your_client_id_here\n\n# Your Microsoft Azure App Client Secret\nMICROSOFT_CLIENT_SECRET=your_client_secret_here\n"
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
"integration:xero": {
|
|
339
|
+
"files": {
|
|
340
|
+
"lib/xero-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst XERO_BASE_URL = \"https://api.xero.com/api.xro/2.0\";\n\ninterface XeroResponse<T> {\n Id: string;\n Status: string;\n ProviderName: string;\n DateTimeUTC: string;\n [key: string]: T | string;\n}\n\ninterface XeroInvoice {\n InvoiceID: string;\n InvoiceNumber: string;\n Type: \"ACCREC\" | \"ACCPAY\";\n Status: string;\n LineAmountTypes: string;\n Contact: {\n ContactID: string;\n Name: string;\n };\n LineItems: Array<{\n LineItemID?: string;\n Description: string;\n Quantity: number;\n UnitAmount: number;\n AccountCode?: string;\n TaxType?: string;\n LineAmount: number;\n }>;\n Date: string;\n DueDate: string;\n SubTotal: number;\n TotalTax: number;\n Total: number;\n AmountDue: number;\n AmountPaid: number;\n CurrencyCode: string;\n Reference?: string;\n UpdatedDateUTC: string;\n}\n\ninterface XeroContact {\n ContactID: string;\n ContactNumber?: string;\n Name: string;\n FirstName?: string;\n LastName?: string;\n EmailAddress?: string;\n Addresses?: Array<{\n AddressType: string;\n City?: string;\n Region?: string;\n PostalCode?: string;\n Country?: string;\n AttentionTo?: string;\n }>;\n Phones?: Array<{\n PhoneType: string;\n PhoneNumber?: string;\n PhoneAreaCode?: string;\n PhoneCountryCode?: string;\n }>;\n UpdatedDateUTC: string;\n IsSupplier: boolean;\n IsCustomer: boolean;\n}\n\ninterface XeroTenant {\n tenantId: string;\n tenantType: string;\n tenantName: string;\n}\n\nasync function requireAccessToken(): Promise<string> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Xero. Please connect your account.\");\n }\n return token;\n}\n\nasync function getTenantId(token: string): Promise<string> {\n const response = await fetch(\"https://api.xero.com/connections\", {\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to get Xero tenants: ${response.status}`);\n }\n\n const tenants: XeroTenant[] = await response.json();\n const tenantId = tenants[0]?.tenantId;\n if (!tenantId) {\n throw new Error(\n \"No Xero organizations found. Please connect to a Xero organization.\",\n );\n }\n\n return tenantId;\n}\n\nfunction getCollection<T>(response: XeroResponse<T>, key: keyof T): T[keyof T] {\n return response[key] as T[keyof T];\n}\n\nasync function xeroFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await requireAccessToken();\n const tenantId = await getTenantId(token);\n\n const response = await fetch(`${XERO_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"xero-tenant-id\": tenantId,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...(options.headers ?? {}),\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as Record<string, unknown>));\n const message =\n (error as { Message?: string; Detail?: string }).Message ??\n (error as { Message?: string; Detail?: string }).Detail ??\n response.statusText;\n\n throw new Error(`Xero API error: ${response.status} ${message}`);\n }\n\n return response.json();\n}\n\nexport async function listInvoices(options?: {\n status?: \"DRAFT\" | \"SUBMITTED\" | \"AUTHORISED\" | \"PAID\" | \"VOIDED\";\n type?: \"ACCREC\" | \"ACCPAY\";\n contactId?: string;\n limit?: number;\n}): Promise<XeroInvoice[]> {\n const params = new URLSearchParams();\n const where: string[] = [];\n\n if (options?.status) where.push(`Status == \"${options.status}\"`);\n if (options?.type) where.push(`Type == \"${options.type}\"`);\n if (options?.contactId) {\n where.push(`Contact.ContactID == Guid(\"${options.contactId}\")`);\n }\n\n if (where.length) params.set(\"where\", where.join(\" AND \"));\n if (options?.limit) params.set(\"page\", \"1\");\n\n const queryString = params.toString();\n const endpoint = `/Invoices${queryString ? `?${queryString}` : \"\"}`;\n\n const response = await xeroFetch<XeroResponse<{ Invoices: XeroInvoice[] }>>(endpoint);\n const invoices = (getCollection(response, \"Invoices\") as XeroInvoice[]) ?? [];\n\n return options?.limit ? invoices.slice(0, options.limit) : invoices;\n}\n\nexport async function getInvoice(invoiceId: string): Promise<XeroInvoice> {\n const response = await xeroFetch<XeroResponse<{ Invoices: XeroInvoice[] }>>(\n `/Invoices/${invoiceId}`,\n );\n const invoices = getCollection(response, \"Invoices\") as XeroInvoice[] | undefined;\n\n const invoice = invoices?.[0];\n if (!invoice) throw new Error(`Invoice not found: ${invoiceId}`);\n\n return invoice;\n}\n\nexport async function createInvoice(options: {\n contactId: string;\n type: \"ACCREC\" | \"ACCPAY\";\n date: string;\n dueDate: string;\n lineItems: Array<{\n description: string;\n quantity: number;\n unitAmount: number;\n accountCode?: string;\n taxType?: string;\n }>;\n reference?: string;\n status?: \"DRAFT\" | \"SUBMITTED\" | \"AUTHORISED\";\n}): Promise<XeroInvoice> {\n const invoice = {\n Type: options.type,\n Contact: { ContactID: options.contactId },\n Date: options.date,\n DueDate: options.dueDate,\n LineItems: options.lineItems.map((item) => ({\n Description: item.description,\n Quantity: item.quantity,\n UnitAmount: item.unitAmount,\n AccountCode: item.accountCode,\n TaxType: item.taxType ?? \"NONE\",\n })),\n Reference: options.reference,\n Status: options.status ?? \"DRAFT\",\n LineAmountTypes: \"Exclusive\",\n };\n\n const response = await xeroFetch<XeroResponse<{ Invoices: XeroInvoice[] }>>(\n \"/Invoices\",\n {\n method: \"POST\",\n body: JSON.stringify({ Invoices: [invoice] }),\n },\n );\n\n const invoices = getCollection(response, \"Invoices\") as XeroInvoice[] | undefined;\n const created = invoices?.[0];\n if (!created) throw new Error(\"Failed to create invoice\");\n\n return created;\n}\n\nexport async function listContacts(options?: {\n isCustomer?: boolean;\n isSupplier?: boolean;\n limit?: number;\n}): Promise<XeroContact[]> {\n const params = new URLSearchParams();\n const where: string[] = [];\n\n if (options?.isCustomer !== undefined) where.push(`IsCustomer == ${options.isCustomer}`);\n if (options?.isSupplier !== undefined) where.push(`IsSupplier == ${options.isSupplier}`);\n\n if (where.length) params.set(\"where\", where.join(\" AND \"));\n\n const queryString = params.toString();\n const endpoint = `/Contacts${queryString ? `?${queryString}` : \"\"}`;\n\n const response = await xeroFetch<XeroResponse<{ Contacts: XeroContact[] }>>(endpoint);\n const contacts = (getCollection(response, \"Contacts\") as XeroContact[]) ?? [];\n\n return options?.limit ? contacts.slice(0, options.limit) : contacts;\n}\n\nexport async function getContact(contactId: string): Promise<XeroContact> {\n const response = await xeroFetch<XeroResponse<{ Contacts: XeroContact[] }>>(\n `/Contacts/${contactId}`,\n );\n const contacts = getCollection(response, \"Contacts\") as XeroContact[] | undefined;\n\n const contact = contacts?.[0];\n if (!contact) throw new Error(`Contact not found: ${contactId}`);\n\n return contact;\n}\n\nexport async function getCurrentUser(): Promise<{\n userId: string;\n userName: string;\n email: string;\n}> {\n const token = await requireAccessToken();\n\n const response = await fetch(`${XERO_BASE_URL}/Organisation`, {\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to get user info: ${response.status}`);\n }\n\n const data: { Organisations: Array<{ Name: string }> } = await response.json();\n\n return {\n userId: \"current-user\",\n userName: data.Organisations[0]?.Name ?? \"Xero User\",\n email: \"user@xero.com\",\n };\n}\n",
|
|
341
|
+
"tools/list-invoices.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listInvoices } from \"../../lib/xero-client.ts\";\n\nexport default tool({\n id: \"list-invoices\",\n description: \"List invoices from Xero. Can filter by status, type, or contact.\",\n inputSchema: z.object({\n status: z\n .enum([\"DRAFT\", \"SUBMITTED\", \"AUTHORISED\", \"PAID\", \"VOIDED\"])\n .optional()\n .describe(\"Filter by invoice status\"),\n type: z\n .enum([\"ACCREC\", \"ACCPAY\"])\n .optional()\n .describe(\"Filter by invoice type (ACCREC = sales invoice, ACCPAY = bill)\"),\n contactId: z.string().optional().describe(\"Filter by contact ID\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of invoices to return\"),\n }),\n async execute({ status, type, contactId, limit }) {\n const invoices = await listInvoices({ status, type, contactId, limit });\n\n return invoices.map((invoice) => ({\n invoiceId: invoice.InvoiceID,\n invoiceNumber: invoice.InvoiceNumber,\n type: invoice.Type,\n status: invoice.Status,\n contact: invoice.Contact.Name,\n date: invoice.Date,\n dueDate: invoice.DueDate,\n subTotal: invoice.SubTotal,\n totalTax: invoice.TotalTax,\n total: invoice.Total,\n amountDue: invoice.AmountDue,\n amountPaid: invoice.AmountPaid,\n currencyCode: invoice.CurrencyCode,\n reference: invoice.Reference,\n }));\n },\n});\n",
|
|
342
|
+
"tools/list-contacts.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listContacts } from \"../../lib/xero-client.ts\";\n\nexport default tool({\n id: \"list-contacts\",\n description: \"List contacts from Xero. Can filter by customer or supplier type.\",\n inputSchema: z.object({\n isCustomer: z.boolean().optional().describe(\"Filter for customers only\"),\n isSupplier: z.boolean().optional().describe(\"Filter for suppliers only\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of contacts to return\"),\n }),\n async execute({ isCustomer, isSupplier, limit }) {\n const contacts = await listContacts({ isCustomer, isSupplier, limit });\n\n return contacts.map((contact) => ({\n contactId: contact.ContactID,\n contactNumber: contact.ContactNumber,\n name: contact.Name,\n firstName: contact.FirstName,\n lastName: contact.LastName,\n emailAddress: contact.EmailAddress,\n isCustomer: contact.IsCustomer,\n isSupplier: contact.IsSupplier,\n addresses: contact.Addresses?.map((addr) => ({\n addressType: addr.AddressType,\n city: addr.City,\n region: addr.Region,\n postalCode: addr.PostalCode,\n country: addr.Country,\n })),\n phones: contact.Phones?.map((phone) => ({\n phoneType: phone.PhoneType,\n phoneNumber: phone.PhoneNumber,\n })),\n }));\n },\n});\n",
|
|
343
|
+
"tools/get-contact.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getContact } from \"../../lib/xero-client.ts\";\n\nexport default tool({\n id: \"get-contact\",\n description: \"Get details of a specific Xero contact by its ID.\",\n inputSchema: z.object({\n contactId: z.string().describe(\"The ID of the contact to retrieve\"),\n }),\n async execute({ contactId }) {\n const contact = await getContact(contactId);\n\n return {\n contactId: contact.ContactID,\n contactNumber: contact.ContactNumber,\n name: contact.Name,\n firstName: contact.FirstName,\n lastName: contact.LastName,\n emailAddress: contact.EmailAddress,\n isCustomer: contact.IsCustomer,\n isSupplier: contact.IsSupplier,\n addresses: contact.Addresses?.map((addr) => ({\n addressType: addr.AddressType,\n city: addr.City,\n region: addr.Region,\n postalCode: addr.PostalCode,\n country: addr.Country,\n attentionTo: addr.AttentionTo,\n })),\n phones: contact.Phones?.map((phone) => ({\n phoneType: phone.PhoneType,\n phoneNumber: phone.PhoneNumber,\n phoneAreaCode: phone.PhoneAreaCode,\n phoneCountryCode: phone.PhoneCountryCode,\n })),\n updatedDateUTC: contact.UpdatedDateUTC,\n };\n },\n});\n",
|
|
344
|
+
"tools/get-invoice.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getInvoice } from \"../../lib/xero-client.ts\";\n\nexport default tool({\n id: \"get-invoice\",\n description: \"Get details of a specific Xero invoice by its ID.\",\n inputSchema: z.object({\n invoiceId: z.string().describe(\"The ID of the invoice to retrieve\"),\n }),\n async execute({ invoiceId }) {\n const invoice = await getInvoice(invoiceId);\n\n return {\n invoiceId: invoice.InvoiceID,\n invoiceNumber: invoice.InvoiceNumber,\n type: invoice.Type,\n status: invoice.Status,\n contact: {\n contactId: invoice.Contact.ContactID,\n name: invoice.Contact.Name,\n },\n lineItems: invoice.LineItems.map((item) => ({\n lineItemId: item.LineItemID,\n description: item.Description,\n quantity: item.Quantity,\n unitAmount: item.UnitAmount,\n lineAmount: item.LineAmount,\n accountCode: item.AccountCode,\n taxType: item.TaxType,\n })),\n date: invoice.Date,\n dueDate: invoice.DueDate,\n subTotal: invoice.SubTotal,\n totalTax: invoice.TotalTax,\n total: invoice.Total,\n amountDue: invoice.AmountDue,\n amountPaid: invoice.AmountPaid,\n currencyCode: invoice.CurrencyCode,\n reference: invoice.Reference,\n updatedDateUTC: invoice.UpdatedDateUTC,\n };\n },\n});\n",
|
|
345
|
+
"tools/create-invoice.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createInvoice } from \"../../lib/xero-client.ts\";\n\nexport default tool({\n id: \"create-invoice\",\n description: \"Create a new invoice in Xero.\",\n inputSchema: z.object({\n contactId: z.string().describe(\"The ID of the contact for the invoice\"),\n type: z\n .enum([\"ACCREC\", \"ACCPAY\"])\n .describe(\"Invoice type (ACCREC = sales invoice, ACCPAY = bill)\"),\n date: z.string().describe(\"Invoice date in YYYY-MM-DD format\"),\n dueDate: z.string().describe(\"Due date in YYYY-MM-DD format\"),\n lineItems: z\n .array(\n z.object({\n description: z.string().describe(\"Line item description\"),\n quantity: z.number().describe(\"Quantity\"),\n unitAmount: z.number().describe(\"Unit price/amount\"),\n accountCode: z.string().optional().describe(\"Account code\"),\n taxType: z\n .string()\n .optional()\n .describe(\"Tax type (e.g., 'NONE', 'OUTPUT2', 'INPUT2')\"),\n }),\n )\n .describe(\"Line items for the invoice\"),\n reference: z.string().optional().describe(\"Optional reference number\"),\n status: z\n .enum([\"DRAFT\", \"SUBMITTED\", \"AUTHORISED\"])\n .optional()\n .describe(\"Invoice status (defaults to DRAFT)\"),\n }),\n async execute({ contactId, type, date, dueDate, lineItems, reference, status }) {\n const invoice = await createInvoice({\n contactId,\n type,\n date,\n dueDate,\n lineItems,\n reference,\n status,\n });\n\n return {\n success: true,\n invoice: {\n invoiceId: invoice.InvoiceID,\n invoiceNumber: invoice.InvoiceNumber,\n type: invoice.Type,\n status: invoice.Status,\n contact: invoice.Contact.Name,\n date: invoice.Date,\n dueDate: invoice.DueDate,\n total: invoice.Total,\n amountDue: invoice.AmountDue,\n },\n };\n },\n});\n",
|
|
346
|
+
"app/api/auth/xero/route.ts": "import { createOAuthInitHandler, xeroConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(xeroConfig);\n",
|
|
347
|
+
"app/api/auth/xero/callback/route.ts": "import { createOAuthCallbackHandler, memoryTokenStore, xeroConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState(state: string) {\n return memoryTokenStore.getState(state);\n },\n setState(state: { state: string; codeVerifier?: string; createdAt: number }) {\n return memoryTokenStore.setState(state);\n },\n clearState(state: string) {\n return memoryTokenStore.clearState(state);\n },\n};\n\nexport const GET = createOAuthCallbackHandler(xeroConfig, { tokenStore: hybridTokenStore });\n",
|
|
348
|
+
".env.example": "# Xero OAuth Configuration\n# Get your credentials from https://developer.xero.com/app/manage\nXERO_CLIENT_ID=your-client-id\nXERO_CLIENT_SECRET=your-client-secret\n"
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"integration:clickup": {
|
|
352
|
+
"files": {
|
|
353
|
+
"lib/clickup-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst CLICKUP_BASE_URL = \"https://api.clickup.com/api/v2\";\n\ninterface ClickUpTask {\n id: string;\n name: string;\n description: string;\n status: {\n status: string;\n color: string;\n type: string;\n };\n date_created: string;\n date_updated: string;\n date_closed: string | null;\n creator: {\n id: number;\n username: string;\n email: string;\n };\n assignees: Array<{\n id: number;\n username: string;\n email: string;\n }>;\n tags: Array<{\n name: string;\n tag_fg: string;\n tag_bg: string;\n }>;\n due_date: string | null;\n start_date: string | null;\n priority: {\n id: string;\n priority: string;\n color: string;\n } | null;\n list: {\n id: string;\n name: string;\n };\n folder: {\n id: string;\n name: string;\n };\n space: {\n id: string;\n name: string;\n };\n}\n\ninterface ClickUpList {\n id: string;\n name: string;\n orderindex: number;\n content: string;\n status: {\n status: string;\n color: string;\n };\n priority: {\n priority: string;\n color: string;\n } | null;\n assignee: {\n id: number;\n username: string;\n email: string;\n } | null;\n task_count: number;\n due_date: string | null;\n start_date: string | null;\n folder: {\n id: string;\n name: string;\n hidden: boolean;\n access: boolean;\n };\n space: {\n id: string;\n name: string;\n access: boolean;\n };\n archived: boolean;\n}\n\ninterface ClickUpFolder {\n id: string;\n name: string;\n orderindex: number;\n override_statuses: boolean;\n hidden: boolean;\n space: {\n id: string;\n name: string;\n };\n task_count: string;\n lists: ClickUpList[];\n}\n\ninterface ClickUpSpace {\n id: string;\n name: string;\n private: boolean;\n statuses: Array<{\n status: string;\n type: string;\n orderindex: number;\n color: string;\n }>;\n multiple_assignees: boolean;\n features: {\n due_dates: {\n enabled: boolean;\n start_date: boolean;\n remap_due_dates: boolean;\n remap_closed_due_date: boolean;\n };\n time_tracking: {\n enabled: boolean;\n };\n tags: {\n enabled: boolean;\n };\n time_estimates: {\n enabled: boolean;\n };\n checklists: {\n enabled: boolean;\n };\n custom_fields: {\n enabled: boolean;\n };\n remap_dependencies: {\n enabled: boolean;\n };\n dependency_warning: {\n enabled: boolean;\n };\n portfolios: {\n enabled: boolean;\n };\n };\n}\n\nfunction buildCustomTaskParams(options?: {\n customTaskIds?: boolean;\n teamId?: string;\n}): URLSearchParams {\n const params = new URLSearchParams();\n\n if (options?.customTaskIds) {\n params.set(\"custom_task_ids\", \"true\");\n if (options.teamId) params.set(\"team_id\", options.teamId);\n }\n\n return params;\n}\n\nfunction withQuery(endpoint: string, params: URLSearchParams): string {\n const queryString = params.toString();\n return queryString ? `${endpoint}?${queryString}` : endpoint;\n}\n\nasync function clickupFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with ClickUp. Please connect your account.\");\n }\n\n const response = await fetch(`${CLICKUP_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: token,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as Record<string, unknown>));\n const message =\n (error as { err?: string; error?: string }).err ??\n (error as { err?: string; error?: string }).error ??\n response.statusText;\n\n throw new Error(`ClickUp API error: ${response.status} ${message}`);\n }\n\n return response.json();\n}\n\nexport async function listSpaces(teamId: string): Promise<ClickUpSpace[]> {\n const response = await clickupFetch<{ spaces: ClickUpSpace[] }>(`/team/${teamId}/space`);\n return response.spaces;\n}\n\nexport async function listFolders(spaceId: string): Promise<ClickUpFolder[]> {\n const response = await clickupFetch<{ folders: ClickUpFolder[] }>(`/space/${spaceId}/folder`);\n return response.folders;\n}\n\nexport async function listLists(folderId: string): Promise<ClickUpList[]> {\n const response = await clickupFetch<{ lists: ClickUpList[] }>(`/folder/${folderId}/list`);\n return response.lists;\n}\n\nexport async function listFolderlessLists(spaceId: string): Promise<ClickUpList[]> {\n const response = await clickupFetch<{ lists: ClickUpList[] }>(`/space/${spaceId}/list`);\n return response.lists;\n}\n\nexport async function listTasks(options: {\n listId?: string;\n spaceId?: string;\n folderId?: string;\n assignees?: number[];\n statuses?: string[];\n includeClosed?: boolean;\n orderBy?: string;\n subtasks?: boolean;\n}): Promise<ClickUpTask[]> {\n const params = new URLSearchParams();\n\n options.assignees?.forEach((assignee) => params.append(\"assignees[]\", assignee.toString()));\n options.statuses?.forEach((status) => params.append(\"statuses[]\", status));\n\n if (options.includeClosed !== undefined) params.set(\"include_closed\", options.includeClosed.toString());\n if (options.orderBy) params.set(\"order_by\", options.orderBy);\n if (options.subtasks !== undefined) params.set(\"subtasks\", options.subtasks.toString());\n\n let endpoint = \"/task\";\n if (options.listId) endpoint = `/list/${options.listId}/task`;\n else if (options.folderId) endpoint = `/folder/${options.folderId}/task`;\n else if (options.spaceId) endpoint = `/space/${options.spaceId}/task`;\n\n const response = await clickupFetch<{ tasks: ClickUpTask[] }>(withQuery(endpoint, params));\n return response.tasks;\n}\n\nexport async function getTask(\n taskId: string,\n options?: {\n customTaskIds?: boolean;\n teamId?: string;\n includeSubtasks?: boolean;\n },\n): Promise<ClickUpTask> {\n const params = buildCustomTaskParams(options);\n\n if (options?.includeSubtasks) params.set(\"include_subtasks\", \"true\");\n\n return clickupFetch<ClickUpTask>(withQuery(`/task/${taskId}`, params));\n}\n\nexport async function createTask(options: {\n listId: string;\n name: string;\n description?: string;\n assignees?: number[];\n tags?: string[];\n status?: string;\n priority?: number;\n dueDate?: number;\n dueDateTime?: boolean;\n timeEstimate?: number;\n startDate?: number;\n startDateTime?: boolean;\n notifyAll?: boolean;\n parent?: string;\n linksTo?: string;\n checkRequired?: boolean;\n customTaskIds?: boolean;\n teamId?: string;\n}): Promise<ClickUpTask> {\n const body: Record<string, unknown> = { name: options.name };\n\n if (options.description) body.description = options.description;\n if (options.assignees) body.assignees = options.assignees;\n if (options.tags) body.tags = options.tags;\n if (options.status) body.status = options.status;\n if (options.priority !== undefined) body.priority = options.priority;\n\n if (options.dueDate) {\n body.due_date = options.dueDate;\n if (options.dueDateTime !== undefined) body.due_date_time = options.dueDateTime;\n }\n\n if (options.timeEstimate) body.time_estimate = options.timeEstimate;\n\n if (options.startDate) {\n body.start_date = options.startDate;\n if (options.startDateTime !== undefined) body.start_date_time = options.startDateTime;\n }\n\n if (options.notifyAll !== undefined) body.notify_all = options.notifyAll;\n if (options.parent) body.parent = options.parent;\n if (options.linksTo) body.links_to = options.linksTo;\n\n const url = withQuery(`/list/${options.listId}/task`, buildCustomTaskParams(options));\n\n return clickupFetch<ClickUpTask>(url, {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport async function updateTask(\n taskId: string,\n updates: {\n name?: string;\n description?: string;\n status?: string;\n priority?: number | null;\n dueDate?: number | null;\n dueDateTime?: boolean;\n timeEstimate?: number | null;\n startDate?: number | null;\n startDateTime?: boolean;\n assignees?: {\n add?: number[];\n rem?: number[];\n };\n archived?: boolean;\n },\n options?: {\n customTaskIds?: boolean;\n teamId?: string;\n },\n): Promise<ClickUpTask> {\n const body: Record<string, unknown> = {};\n\n if (updates.name !== undefined) body.name = updates.name;\n if (updates.description !== undefined) body.description = updates.description;\n if (updates.status !== undefined) body.status = updates.status;\n if (updates.priority !== undefined) body.priority = updates.priority;\n\n if (updates.dueDate !== undefined) {\n body.due_date = updates.dueDate;\n if (updates.dueDateTime !== undefined) body.due_date_time = updates.dueDateTime;\n }\n\n if (updates.timeEstimate !== undefined) body.time_estimate = updates.timeEstimate;\n\n if (updates.startDate !== undefined) {\n body.start_date = updates.startDate;\n if (updates.startDateTime !== undefined) body.start_date_time = updates.startDateTime;\n }\n\n if (updates.assignees) body.assignees = updates.assignees;\n if (updates.archived !== undefined) body.archived = updates.archived;\n\n const url = withQuery(`/task/${taskId}`, buildCustomTaskParams(options));\n\n return clickupFetch<ClickUpTask>(url, {\n method: \"PUT\",\n body: JSON.stringify(body),\n });\n}\n\nexport async function getAuthorizedUser(): Promise<{\n user: {\n id: number;\n username: string;\n email: string;\n color: string;\n profilePicture: string;\n };\n}> {\n return clickupFetch<{\n user: {\n id: number;\n username: string;\n email: string;\n color: string;\n profilePicture: string;\n };\n }>(\"/user\");\n}\n\nexport async function getTeams(): Promise<\n Array<{\n id: string;\n name: string;\n color: string;\n avatar: string | null;\n members: Array<{\n user: {\n id: number;\n username: string;\n email: string;\n };\n }>;\n }>\n> {\n const response = await clickupFetch<{\n teams: Array<{\n id: string;\n name: string;\n color: string;\n avatar: string | null;\n members: Array<{\n user: {\n id: number;\n username: string;\n email: string;\n };\n }>;\n }>;\n }>(\"/team\");\n\n return response.teams;\n}\n",
|
|
354
|
+
"tools/list-tasks.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getAuthorizedUser, getTeams, listSpaces, listTasks } from \"../../lib/clickup-client.ts\";\n\nexport default tool({\n id: \"list-tasks\",\n description:\n \"List tasks from ClickUp. Can filter by list, folder, space, or get tasks assigned to the current user.\",\n inputSchema: z.object({\n listId: z.string().optional().describe(\"List ID to list tasks from\"),\n folderId: z.string().optional().describe(\"Folder ID to list tasks from\"),\n spaceId: z.string().optional().describe(\"Space ID to list tasks from\"),\n assignedToMe: z.boolean().default(false).describe(\"List tasks assigned to the current user\"),\n includeClosed: z.boolean().default(false).describe(\"Include completed/closed tasks\"),\n statuses: z.array(z.string()).optional().describe(\"Filter by specific status names\"),\n orderBy: z\n .string()\n .optional()\n .describe(\"Order results by field (e.g., 'due_date', 'created', 'updated')\"),\n limit: z.number().min(1).max(50).default(20).describe(\"Maximum number of tasks to return\"),\n }),\n async execute({ listId, folderId, spaceId, assignedToMe, includeClosed, statuses, orderBy, limit }) {\n let tasks;\n\n if (assignedToMe) {\n const user = await getAuthorizedUser();\n const teams = await getTeams();\n const teamId = teams[0]?.id;\n\n if (!teamId) return { tasks: [], message: \"No teams found\" };\n\n const spaces = await listSpaces(teamId);\n const firstSpaceId = spaces[0]?.id;\n\n if (!firstSpaceId) return { tasks: [], message: \"No spaces found in team\" };\n\n tasks = await listTasks({\n spaceId: firstSpaceId,\n assignees: [user.user.id],\n includeClosed,\n statuses,\n orderBy,\n });\n } else if (listId) {\n tasks = await listTasks({ listId, includeClosed, statuses, orderBy });\n } else if (folderId) {\n tasks = await listTasks({ folderId, includeClosed, statuses, orderBy });\n } else if (spaceId) {\n tasks = await listTasks({ spaceId, includeClosed, statuses, orderBy });\n } else {\n return {\n tasks: [],\n message: \"Please specify either a listId, folderId, spaceId, or set assignedToMe to true\",\n };\n }\n\n return tasks.slice(0, limit).map((task) => ({\n id: task.id,\n name: task.name,\n status: task.status.status,\n dueDate: task.due_date ? new Date(parseInt(task.due_date)).toISOString() : null,\n priority: task.priority?.priority ?? \"none\",\n assignees: task.assignees.map((a) => a.username),\n tags: task.tags.map((t) => t.name),\n list: task.list.name,\n folder: task.folder.name,\n space: task.space.name,\n }));\n },\n});\n",
|
|
355
|
+
"tools/get-task.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getTask } from \"../../lib/clickup-client.ts\";\n\nfunction toIsoDate(value?: string | null): string | null {\n if (!value) return null;\n return new Date(Number.parseInt(value, 10)).toISOString();\n}\n\nexport default tool({\n id: \"get-task\",\n description: \"Get detailed information about a specific ClickUp task by ID.\",\n inputSchema: z.object({\n taskId: z.string().describe(\"The ID of the task to retrieve\"),\n includeSubtasks: z.boolean().default(false).describe(\"Include subtasks in the response\"),\n customTaskIds: z.boolean().default(false).describe(\"Use custom task IDs instead of internal IDs\"),\n teamId: z.string().optional().describe(\"Team ID (required when using custom task IDs)\"),\n }),\n async execute({ taskId, includeSubtasks, customTaskIds, teamId }) {\n const task = await getTask(taskId, { includeSubtasks, customTaskIds, teamId });\n\n return {\n id: task.id,\n name: task.name,\n description: task.description,\n status: task.status.status,\n priority: task.priority?.priority ?? \"none\",\n dueDate: toIsoDate(task.due_date),\n startDate: toIsoDate(task.start_date),\n dateCreated: task.date_created,\n dateUpdated: task.date_updated,\n dateClosed: task.date_closed,\n creator: {\n id: task.creator.id,\n username: task.creator.username,\n email: task.creator.email,\n },\n assignees: task.assignees.map(({ id, username, email }) => ({ id, username, email })),\n tags: task.tags.map(({ name }) => name),\n list: {\n id: task.list.id,\n name: task.list.name,\n },\n folder: {\n id: task.folder.id,\n name: task.folder.name,\n },\n space: {\n id: task.space.id,\n name: task.space.name,\n },\n };\n },\n});\n",
|
|
356
|
+
"tools/create-task.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createTask } from \"../../lib/clickup-client.ts\";\n\nexport default tool({\n id: \"create-task\",\n description: \"Create a new task in a ClickUp list.\",\n inputSchema: z.object({\n listId: z.string().describe(\"The ID of the list to create the task in\"),\n name: z.string().describe(\"The name/title of the task\"),\n description: z.string().optional().describe(\"Description or details for the task\"),\n assignees: z.array(z.number()).optional().describe(\"Array of user IDs to assign the task to\"),\n tags: z.array(z.string()).optional().describe(\"Array of tag names to add to the task\"),\n status: z.string().optional().describe(\"Status name for the task\"),\n priority: z\n .number()\n .min(1)\n .max(4)\n .optional()\n .describe(\"Priority level: 1 (urgent), 2 (high), 3 (normal), 4 (low)\"),\n dueDate: z.number().optional().describe(\"Due date in Unix timestamp (milliseconds)\"),\n startDate: z.number().optional().describe(\"Start date in Unix timestamp (milliseconds)\"),\n timeEstimate: z.number().optional().describe(\"Time estimate in milliseconds\"),\n notifyAll: z.boolean().default(false).describe(\"Notify all assignees when task is created\"),\n }),\n async execute(input) {\n const task = await createTask(input);\n\n return {\n success: true,\n task: {\n id: task.id,\n name: task.name,\n status: task.status.status,\n dueDate: task.due_date ? new Date(Number(task.due_date)).toISOString() : null,\n priority: task.priority?.priority ?? \"none\",\n assignees: task.assignees.map((a) => a.username),\n list: task.list.name,\n url: `https://app.clickup.com/t/${task.id}`,\n },\n };\n },\n});\n",
|
|
357
|
+
"tools/update-task.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { updateTask } from \"../../lib/clickup-client.ts\";\n\nexport default tool({\n id: \"update-task\",\n description: \"Update an existing ClickUp task.\",\n inputSchema: z.object({\n taskId: z.string().describe(\"The ID of the task to update\"),\n name: z.string().optional().describe(\"New name/title for the task\"),\n description: z.string().optional().describe(\"New description for the task\"),\n status: z.string().optional().describe(\"New status name for the task\"),\n priority: z\n .number()\n .min(1)\n .max(4)\n .optional()\n .describe(\n \"Priority level: 1 (urgent), 2 (high), 3 (normal), 4 (low). Use null to remove priority.\",\n ),\n dueDate: z\n .number()\n .optional()\n .describe(\"Due date in Unix timestamp (milliseconds). Use null to remove due date.\"),\n startDate: z\n .number()\n .optional()\n .describe(\"Start date in Unix timestamp (milliseconds). Use null to remove start date.\"),\n timeEstimate: z\n .number()\n .optional()\n .describe(\"Time estimate in milliseconds. Use null to remove time estimate.\"),\n addAssignees: z.array(z.number()).optional().describe(\"Array of user IDs to add as assignees\"),\n removeAssignees: z\n .array(z.number())\n .optional()\n .describe(\"Array of user IDs to remove from assignees\"),\n archived: z.boolean().optional().describe(\"Archive or unarchive the task\"),\n customTaskIds: z.boolean().default(false).describe(\"Use custom task IDs instead of internal IDs\"),\n teamId: z.string().optional().describe(\"Team ID (required when using custom task IDs)\"),\n }),\n async execute({\n taskId,\n name,\n description,\n status,\n priority,\n dueDate,\n startDate,\n timeEstimate,\n addAssignees,\n removeAssignees,\n archived,\n customTaskIds,\n teamId,\n }) {\n const updates: Record<string, unknown> = {\n ...(name !== undefined ? { name } : {}),\n ...(description !== undefined ? { description } : {}),\n ...(status !== undefined ? { status } : {}),\n ...(priority !== undefined ? { priority } : {}),\n ...(dueDate !== undefined ? { dueDate } : {}),\n ...(startDate !== undefined ? { startDate } : {}),\n ...(timeEstimate !== undefined ? { timeEstimate } : {}),\n ...(archived !== undefined ? { archived } : {}),\n ...(addAssignees || removeAssignees\n ? {\n assignees: {\n ...(addAssignees ? { add: addAssignees } : {}),\n ...(removeAssignees ? { rem: removeAssignees } : {}),\n },\n }\n : {}),\n };\n\n const task = await updateTask(\n taskId,\n updates,\n customTaskIds ? { customTaskIds, teamId } : undefined,\n );\n\n return {\n success: true,\n task: {\n id: task.id,\n name: task.name,\n status: task.status.status,\n dueDate: task.due_date ? new Date(Number(task.due_date)).toISOString() : null,\n priority: task.priority?.priority ?? \"none\",\n assignees: task.assignees.map((a) => a.username),\n url: `https://app.clickup.com/t/${task.id}`,\n },\n };\n },\n});\n",
|
|
358
|
+
"tools/list-lists.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport {\n getTeams,\n listFolders,\n listFolderlessLists,\n listLists,\n listSpaces,\n} from \"../../lib/clickup-client.ts\";\n\ntype ClickUpList = {\n id: string;\n name: string;\n task_count: number;\n due_date?: string | null;\n status?: { status?: string | null } | null;\n priority?: { priority?: string | null } | null;\n assignee?: { username?: string | null } | null;\n folder: { name: string };\n space: { name: string };\n archived: boolean;\n};\n\nfunction mapList(list: ClickUpList, folderName?: string): {\n id: string;\n name: string;\n taskCount: number;\n dueDate: string | null;\n status: string;\n priority: string;\n assignee: string | null;\n folder: string;\n space: string;\n archived: boolean;\n} {\n return {\n id: list.id,\n name: list.name,\n taskCount: list.task_count,\n dueDate: list.due_date ? new Date(parseInt(list.due_date)).toISOString() : null,\n status: list.status?.status || \"active\",\n priority: list.priority?.priority || \"none\",\n assignee: list.assignee?.username || null,\n folder: folderName ?? list.folder.name,\n space: list.space.name,\n archived: list.archived,\n };\n}\n\nexport default tool({\n id: \"list-lists\",\n description: \"List all lists in ClickUp. Can filter by folder or space. Lists are containers for tasks.\",\n inputSchema: z.object({\n folderId: z.string().optional().describe(\"Folder ID to list lists from\"),\n spaceId: z.string().optional().describe(\"Space ID to list folderless lists from\"),\n includeAll: z\n .boolean()\n .default(false)\n .describe(\"List all lists from all folders in the first space\"),\n }),\n async execute({ folderId, spaceId, includeAll }) {\n if (folderId) {\n const lists = await listLists(folderId);\n return lists.map((list) => mapList(list));\n }\n\n if (spaceId) {\n const lists = await listFolderlessLists(spaceId);\n return lists.map((list) => mapList(list));\n }\n\n if (!includeAll) {\n return {\n lists: [],\n message: \"Please specify either a folderId, spaceId, or set includeAll to true\",\n };\n }\n\n const teams = await getTeams();\n if (teams.length === 0) return { lists: [], message: \"No teams found\" };\n\n const spaces = await listSpaces(teams[0].id);\n if (spaces.length === 0) return { lists: [], message: \"No spaces found in team\" };\n\n const firstSpace = spaces[0];\n const [folders, folderlessLists] = await Promise.all([\n listFolders(firstSpace.id),\n listFolderlessLists(firstSpace.id),\n ]);\n\n const allLists = folderlessLists.map((list) => mapList(list, \"No Folder\"));\n\n for (const folder of folders) {\n const lists = await listLists(folder.id);\n allLists.push(...lists.map((list) => mapList(list, folder.name)));\n }\n\n return allLists;\n },\n});\n",
|
|
359
|
+
"app/api/auth/clickup/route.ts": "import { clickupConfig, createOAuthInitHandler } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(clickupConfig);\n",
|
|
360
|
+
"app/api/auth/clickup/callback/route.ts": "import { clickupConfig, createOAuthCallbackHandler, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(clickupConfig, { tokenStore: hybridTokenStore });\n",
|
|
361
|
+
".env.example": "# ClickUp OAuth Configuration\n# Get your credentials from https://app.clickup.com/settings/apps\nCLICKUP_CLIENT_ID=your-client-id\nCLICKUP_CLIENT_SECRET=your-client-secret\n"
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
"integration:github": {
|
|
365
|
+
"files": {
|
|
366
|
+
"lib/github-client.ts": "/**\n * GitHub API Client\n *\n * Provides a type-safe interface to GitHub API operations.\n */\n\nimport { getValidToken } from \"./oauth.ts\";\n\n// Helper for Cross-Platform environment access\nfunction getEnv(key: string): string | undefined {\n // @ts-ignore - Deno global\n if (typeof Deno !== \"undefined\") return Deno.env.get(key);\n\n // @ts-ignore - process global\n if (typeof process !== \"undefined\" && process.env) return process.env[key];\n\n return undefined;\n}\n\nconst GITHUB_API_BASE = \"https://api.github.com\";\n\nexport interface GitHubRepo {\n id: number;\n name: string;\n full_name: string;\n description: string | null;\n private: boolean;\n html_url: string;\n default_branch: string;\n language: string | null;\n stargazers_count: number;\n forks_count: number;\n open_issues_count: number;\n updated_at: string;\n}\n\nexport interface GitHubPullRequest {\n id: number;\n number: number;\n title: string;\n body: string | null;\n state: \"open\" | \"closed\";\n html_url: string;\n user: { login: string; avatar_url: string };\n created_at: string;\n updated_at: string;\n head: { ref: string; sha: string };\n base: { ref: string };\n mergeable: boolean | null;\n additions: number;\n deletions: number;\n changed_files: number;\n draft: boolean;\n labels: Array<{ name: string; color: string }>;\n}\n\nexport interface GitHubIssue {\n id: number;\n number: number;\n title: string;\n body: string | null;\n state: \"open\" | \"closed\";\n html_url: string;\n user: { login: string };\n created_at: string;\n updated_at: string;\n labels: Array<{ name: string; color: string }>;\n assignees: Array<{ login: string }>;\n}\n\nexport interface GitHubCommit {\n sha: string;\n commit: {\n message: string;\n author: { name: string; date: string };\n };\n html_url: string;\n author: { login: string; avatar_url: string } | null;\n}\n\n/**\n * GitHub OAuth provider configuration\n */\nexport const githubOAuthProvider = {\n name: \"github\",\n authorizationUrl: \"https://github.com/login/oauth/authorize\",\n tokenUrl: \"https://github.com/login/oauth/access_token\",\n clientId: getEnv(\"GITHUB_CLIENT_ID\") || \"\",\n clientSecret: getEnv(\"GITHUB_CLIENT_SECRET\") || \"\",\n scopes: [\"repo\", \"read:user\", \"read:org\"],\n callbackPath: \"/api/auth/github/callback\",\n};\n\nexport function createGitHubClient(userId: string): {\n listRepos(options?: {\n sort?: \"created\" | \"updated\" | \"pushed\" | \"full_name\";\n perPage?: number;\n type?: \"all\" | \"owner\" | \"public\" | \"private\" | \"member\";\n }): Promise<GitHubRepo[]>;\n listPullRequests(\n owner: string,\n repo: string,\n options?: { state?: \"open\" | \"closed\" | \"all\"; perPage?: number },\n ): Promise<GitHubPullRequest[]>;\n getPullRequest(owner: string, repo: string, pullNumber: number): Promise<GitHubPullRequest>;\n getPullRequestDiff(owner: string, repo: string, pullNumber: number): Promise<string>;\n createIssue(\n owner: string,\n repo: string,\n options: { title: string; body?: string; labels?: string[]; assignees?: string[] },\n ): Promise<GitHubIssue>;\n listIssues(\n owner: string,\n repo: string,\n options?: { state?: \"open\" | \"closed\" | \"all\"; perPage?: number },\n ): Promise<GitHubIssue[]>;\n listCommits(\n owner: string,\n repo: string,\n options?: { sha?: string; perPage?: number },\n ): Promise<GitHubCommit[]>;\n getUser(): Promise<{ login: string; name: string; email: string }>;\n} {\n async function getAccessToken(): Promise<string> {\n const token = await getValidToken(githubOAuthProvider, userId, \"github\");\n if (!token) throw new Error(\"GitHub not connected. Please connect your GitHub account first.\");\n return token;\n }\n\n async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const accessToken = await getAccessToken();\n\n const response = await fetch(`${GITHUB_API_BASE}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: \"application/vnd.github+json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`GitHub API error: ${response.status} - ${error}`);\n }\n\n return response.json();\n }\n\n return {\n listRepos(options = {}): Promise<GitHubRepo[]> {\n const params = new URLSearchParams();\n if (options.sort) params.set(\"sort\", options.sort);\n if (options.perPage) params.set(\"per_page\", String(options.perPage));\n if (options.type) params.set(\"type\", options.type);\n\n const query = params.toString();\n return apiRequest<GitHubRepo[]>(`/user/repos${query ? `?${query}` : \"\"}`);\n },\n\n listPullRequests(owner, repo, options = {}): Promise<GitHubPullRequest[]> {\n const params = new URLSearchParams();\n params.set(\"state\", options.state || \"open\");\n if (options.perPage) params.set(\"per_page\", String(options.perPage));\n\n return apiRequest<GitHubPullRequest[]>(\n `/repos/${owner}/${repo}/pulls?${params.toString()}`,\n );\n },\n\n getPullRequest(owner, repo, pullNumber): Promise<GitHubPullRequest> {\n return apiRequest<GitHubPullRequest>(`/repos/${owner}/${repo}/pulls/${pullNumber}`);\n },\n\n async getPullRequestDiff(owner, repo, pullNumber): Promise<string> {\n const accessToken = await getAccessToken();\n\n const response = await fetch(`${GITHUB_API_BASE}/repos/${owner}/${repo}/pulls/${pullNumber}`, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: \"application/vnd.github.diff\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n },\n });\n\n if (!response.ok) throw new Error(`GitHub API error: ${response.status}`);\n\n return response.text();\n },\n\n createIssue(owner, repo, options): Promise<GitHubIssue> {\n return apiRequest<GitHubIssue>(`/repos/${owner}/${repo}/issues`, {\n method: \"POST\",\n body: JSON.stringify(options),\n });\n },\n\n listIssues(owner, repo, options = {}): Promise<GitHubIssue[]> {\n const params = new URLSearchParams();\n params.set(\"state\", options.state || \"open\");\n if (options.perPage) params.set(\"per_page\", String(options.perPage));\n\n return apiRequest<GitHubIssue[]>(`/repos/${owner}/${repo}/issues?${params.toString()}`);\n },\n\n listCommits(owner, repo, options = {}): Promise<GitHubCommit[]> {\n const params = new URLSearchParams();\n if (options.sha) params.set(\"sha\", options.sha);\n if (options.perPage) params.set(\"per_page\", String(options.perPage));\n\n const query = params.toString();\n return apiRequest<GitHubCommit[]>(`/repos/${owner}/${repo}/commits${query ? `?${query}` : \"\"}`);\n },\n\n getUser(): Promise<{ login: string; name: string; email: string }> {\n return apiRequest(\"/user\");\n },\n };\n}\n\nexport type GitHubClient = ReturnType<typeof createGitHubClient>;\n",
|
|
367
|
+
"tools/create-issue.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createGitHubClient } from \"../../lib/github-client.ts\";\n\nexport default tool({\n id: \"create-issue\",\n description: \"Create a new issue in a GitHub repository\",\n inputSchema: z.object({\n repo: z\n .string()\n .describe(\"Repository in format 'owner/repo' (e.g., 'facebook/react')\"),\n title: z.string().min(1).describe(\"Issue title\"),\n body: z\n .string()\n .optional()\n .describe(\"Issue body/description (supports Markdown)\"),\n labels: z.array(z.string()).optional().describe(\"Labels to add to the issue\"),\n assignees: z\n .array(z.string())\n .optional()\n .describe(\"GitHub usernames to assign to the issue\"),\n }),\n execute: async ({ repo, title, body, labels, assignees }, context) => {\n const userId = context?.userId ?? \"current-user\";\n\n const [owner, repoName] = repo.split(\"/\");\n if (!owner || !repoName) {\n return { error: \"Invalid repository format. Use 'owner/repo' format.\" };\n }\n\n try {\n const github = createGitHubClient(userId);\n const issue = await github.createIssue(owner, repoName, {\n title,\n body,\n labels,\n assignees,\n });\n\n return {\n success: true,\n issue: {\n number: issue.number,\n title: issue.title,\n url: issue.html_url,\n state: issue.state,\n labels: issue.labels.map((l: { name: string }) => l.name),\n assignees: issue.assignees.map((a: { login: string }) => a.login),\n },\n message: `Issue #${issue.number} created successfully in ${repo}.`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"GitHub not connected. Please connect your GitHub account.\",\n connectUrl: \"/api/auth/github\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
368
|
+
"tools/get-pr-diff.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createGitHubClient } from \"../../lib/github-client.ts\";\n\nexport default tool({\n id: \"get-pr-diff\",\n description: \"Get the diff for a pull request to review code changes\",\n inputSchema: z.object({\n repo: z\n .string()\n .describe(\"Repository in format 'owner/repo' (e.g., 'facebook/react')\"),\n prNumber: z.number().int().positive().describe(\"Pull request number\"),\n }),\n execute: async ({ repo, prNumber }, context) => {\n // Default to \"current-user\" for development; in production, always pass userId from session\n const userId = context?.userId ?? \"current-user\";\n\n const [owner, repoName] = repo.split(\"/\");\n if (!owner || !repoName) {\n return { error: \"Invalid repository format. Use 'owner/repo' format.\" };\n }\n\n try {\n const github = createGitHubClient(userId);\n\n const pr = await github.getPullRequest(owner, repoName, prNumber);\n const diff = await github.getPullRequestDiff(owner, repoName, prNumber);\n\n const maxDiffLength = 50000;\n const truncatedDiff =\n diff.length > maxDiffLength\n ? `${diff.substring(0, maxDiffLength)}\\n\\n... (diff truncated, ${\n diff.length - maxDiffLength\n } characters remaining)`\n : diff;\n\n return {\n pullRequest: {\n number: pr.number,\n title: pr.title,\n author: pr.user.login,\n url: pr.html_url,\n sourceBranch: pr.head.ref,\n targetBranch: pr.base.ref,\n additions: pr.additions,\n deletions: pr.deletions,\n changedFiles: pr.changed_files,\n isDraft: pr.draft,\n state: pr.state,\n },\n diff: truncatedDiff,\n stats: {\n additions: pr.additions,\n deletions: pr.deletions,\n changedFiles: pr.changed_files,\n },\n message: `Retrieved diff for PR #${prNumber} (${pr.additions} additions, ${pr.deletions} deletions across ${pr.changed_files} files).`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"GitHub not connected. Please connect your GitHub account.\",\n connectUrl: \"/api/auth/github\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
369
|
+
"tools/list-prs.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createGitHubClient } from \"../../lib/github-client.ts\";\n\ntype PullRequest = {\n number: number;\n title: string;\n state: string;\n draft: boolean;\n html_url: string;\n user: { login: string };\n created_at: string;\n updated_at: string;\n head: { ref: string };\n base: { ref: string };\n additions: number;\n deletions: number;\n changed_files: number;\n labels: Array<{ name: string }>;\n};\n\nexport default tool({\n id: \"list-prs\",\n description: \"List pull requests for a GitHub repository\",\n inputSchema: z.object({\n repo: z\n .string()\n .describe(\"Repository in format 'owner/repo' (e.g., 'facebook/react')\"),\n state: z\n .enum([\"open\", \"closed\", \"all\"])\n .default(\"open\")\n .describe(\"State of pull requests to list\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(10)\n .describe(\"Maximum number of pull requests to return\"),\n }),\n execute: async ({ repo, state, limit }, context) => {\n const userId = context?.userId ?? \"current-user\";\n\n const [owner, repoName] = repo.split(\"/\");\n if (!owner || !repoName) {\n return { error: \"Invalid repository format. Use 'owner/repo' format.\" };\n }\n\n try {\n const github = createGitHubClient(userId);\n const prs = await github.listPullRequests(owner, repoName, {\n state,\n perPage: limit,\n });\n\n return {\n pullRequests: prs.map((pr: PullRequest) => ({\n number: pr.number,\n title: pr.title,\n state: pr.state,\n isDraft: pr.draft,\n url: pr.html_url,\n author: pr.user.login,\n createdAt: pr.created_at,\n updatedAt: pr.updated_at,\n sourceBranch: pr.head.ref,\n targetBranch: pr.base.ref,\n additions: pr.additions,\n deletions: pr.deletions,\n changedFiles: pr.changed_files,\n labels: pr.labels.map((l) => l.name),\n })),\n count: prs.length,\n repository: repo,\n message: `Found ${prs.length} ${state} pull request(s) in ${repo}.`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"GitHub not connected. Please connect your GitHub account.\",\n connectUrl: \"/api/auth/github\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
370
|
+
"tools/list-repos.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createGitHubClient } from \"../../lib/github-client.ts\";\n\ntype GitHubRepo = {\n name: string;\n full_name: string;\n description: string | null;\n private: boolean;\n html_url: string;\n default_branch: string;\n language: string | null;\n stargazers_count: number;\n forks_count: number;\n open_issues_count: number;\n updated_at: string;\n};\n\nexport default tool({\n id: \"list-repos\",\n description: \"List GitHub repositories for the authenticated user\",\n inputSchema: z.object({\n type: z\n .enum([\"all\", \"owner\", \"public\", \"private\", \"member\"])\n .default(\"all\")\n .describe(\"Type of repositories to list\"),\n sort: z\n .enum([\"created\", \"updated\", \"pushed\", \"full_name\"])\n .default(\"updated\")\n .describe(\"How to sort the repositories\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of repositories to return\"),\n }),\n execute: async ({ type, sort, limit }, context) => {\n // Default to \"current-user\" for development; in production, always pass userId from session\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const github = createGitHubClient(userId);\n const repos = await github.listRepos({ type, sort, perPage: limit });\n\n return {\n repositories: repos.map((repo: GitHubRepo) => ({\n name: repo.name,\n fullName: repo.full_name,\n description: repo.description ?? null,\n isPrivate: repo.private,\n url: repo.html_url,\n defaultBranch: repo.default_branch,\n language: repo.language,\n stars: repo.stargazers_count,\n forks: repo.forks_count,\n openIssues: repo.open_issues_count,\n updatedAt: repo.updated_at,\n })),\n count: repos.length,\n message: `Found ${repos.length} repository(s).`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"GitHub not connected. Please connect your GitHub account.\",\n connectUrl: \"/api/auth/github\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
371
|
+
"app/api/auth/github/route.ts": "import { createOAuthInitHandler, githubConfig, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(githubConfig, { tokenStore: memoryTokenStore });\n",
|
|
372
|
+
"app/api/auth/github/callback/route.ts": "import { createOAuthCallbackHandler, githubConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(githubConfig, { tokenStore: hybridTokenStore });\n"
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
"integration:servicenow": {
|
|
376
|
+
"files": {
|
|
377
|
+
"lib/servicenow-client.ts": "/**\n * ServiceNow API Client\n *\n * Handles authentication and API calls to ServiceNow REST API.\n */\n\nimport { getServiceNowTokens } from \"./token-store.ts\";\n\nfunction getEnv(name: string): string | undefined {\n if (typeof Deno !== \"undefined\") {\n // @ts-ignore: Deno global\n return Deno.env.get(name);\n }\n // @ts-ignore: Node process\n return globalThis.process?.env?.[name];\n}\n\nexport interface ServiceNowIncident {\n sys_id: string;\n number: string;\n short_description: string;\n description: string;\n state: string;\n priority: string;\n urgency: string;\n impact: string;\n category: string;\n subcategory: string;\n assigned_to: { display_value: string; link: string } | string;\n caller_id: { display_value: string; link: string } | string;\n opened_at: string;\n resolved_at: string | null;\n closed_at: string | null;\n sys_created_on: string;\n sys_updated_on: string;\n}\n\nexport interface ServiceNowKnowledgeArticle {\n sys_id: string;\n number: string;\n short_description: string;\n text: string;\n kb_category: string;\n published: string;\n sys_created_on: string;\n}\n\nexport interface ServiceNowResponse<T> {\n result: T;\n}\n\nexport interface ServiceNowListResponse<T> {\n result: T[];\n}\n\nexport class ServiceNowClient {\n private instance: string;\n private accessToken: string | null = null;\n\n constructor() {\n const instance = getEnv(\"SERVICENOW_INSTANCE\");\n if (!instance) throw new Error(\"SERVICENOW_INSTANCE not configured\");\n\n this.instance = instance.replace(/^https?:\\/\\//, \"\").replace(/\\/$/, \"\");\n }\n\n private get baseUrl(): string {\n return `https://${this.instance}/api/now`;\n }\n\n async ensureAuthenticated(): Promise<void> {\n const tokens = await getServiceNowTokens();\n if (!tokens) {\n throw new Error(\n \"ServiceNow not connected. Please connect via /api/auth/servicenow\",\n );\n }\n this.accessToken = tokens.accessToken;\n }\n\n private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n await this.ensureAuthenticated();\n\n const url = `${this.baseUrl}${endpoint}`;\n const response = await fetch(url, {\n ...options,\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`ServiceNow API error: ${response.status} ${errorText}`);\n }\n\n return response.json();\n }\n\n /**\n * List incidents with optional filters\n */\n async listIncidents(options: {\n limit?: number;\n offset?: number;\n state?: string;\n priority?: string;\n assignedTo?: string;\n query?: string;\n } = {}): Promise<ServiceNowIncident[]> {\n const params = new URLSearchParams({\n sysparm_limit: String(options.limit ?? 20),\n sysparm_offset: String(options.offset ?? 0),\n sysparm_display_value: \"all\",\n });\n\n const queryParts: string[] = [];\n if (options.state) queryParts.push(`state=${options.state}`);\n if (options.priority) queryParts.push(`priority=${options.priority}`);\n if (options.assignedTo) queryParts.push(`assigned_to.name=${options.assignedTo}`);\n if (options.query) queryParts.push(`short_descriptionLIKE${options.query}`);\n\n if (queryParts.length) params.set(\"sysparm_query\", queryParts.join(\"^\"));\n\n const response = await this.request<ServiceNowListResponse<ServiceNowIncident>>(\n `/table/incident?${params}`,\n );\n return response.result;\n }\n\n /**\n * Get a specific incident by sys_id or number\n */\n async getIncident(idOrNumber: string): Promise<ServiceNowIncident> {\n const params = new URLSearchParams({ sysparm_display_value: \"all\" });\n\n if (idOrNumber.toUpperCase().startsWith(\"INC\")) {\n params.set(\"sysparm_query\", `number=${idOrNumber}`);\n const response = await this.request<ServiceNowListResponse<ServiceNowIncident>>(\n `/table/incident?${params}`,\n );\n\n const incident = response.result[0];\n if (!incident) throw new Error(`Incident ${idOrNumber} not found`);\n return incident;\n }\n\n const response = await this.request<ServiceNowResponse<ServiceNowIncident>>(\n `/table/incident/${idOrNumber}?${params}`,\n );\n return response.result;\n }\n\n /**\n * Create a new incident\n */\n async createIncident(data: {\n short_description: string;\n description?: string;\n urgency?: string;\n impact?: string;\n category?: string;\n subcategory?: string;\n caller_id?: string;\n }): Promise<ServiceNowIncident> {\n const response = await this.request<ServiceNowResponse<ServiceNowIncident>>(\n \"/table/incident\",\n {\n method: \"POST\",\n body: JSON.stringify(data),\n },\n );\n return response.result;\n }\n\n /**\n * Update an existing incident\n */\n async updateIncident(\n sysId: string,\n data: Partial<{\n short_description: string;\n description: string;\n state: string;\n urgency: string;\n impact: string;\n assigned_to: string;\n work_notes: string;\n close_notes: string;\n }>,\n ): Promise<ServiceNowIncident> {\n const response = await this.request<ServiceNowResponse<ServiceNowIncident>>(\n `/table/incident/${sysId}`,\n {\n method: \"PATCH\",\n body: JSON.stringify(data),\n },\n );\n return response.result;\n }\n\n /**\n * Search knowledge base articles\n */\n async searchKnowledge(\n query: string,\n limit = 10,\n ): Promise<ServiceNowKnowledgeArticle[]> {\n const params = new URLSearchParams({\n sysparm_limit: String(limit),\n sysparm_query: `short_descriptionLIKE${query}^ORtextLIKE${query}^workflow_state=published`,\n });\n\n const response = await this.request<ServiceNowListResponse<ServiceNowKnowledgeArticle>>(\n `/table/kb_knowledge?${params}`,\n );\n return response.result;\n }\n}\n\n// Singleton instance\nlet client: ServiceNowClient | null = null;\n\nexport function getServiceNowClient(): ServiceNowClient {\n client ??= new ServiceNowClient();\n return client;\n}\n",
|
|
378
|
+
"tools/update-incident.ts": "import { z } from \"zod\";\nimport { getServiceNowClient } from \"../../lib/servicenow-client.ts\";\nimport { isServiceNowConnected } from \"../../lib/token-store.ts\";\n\nexport default defineTool({\n id: \"servicenow-update-incident\",\n description: \"Update an existing incident in ServiceNow\",\n inputSchema: z.object({\n sys_id: z.string().describe(\"The sys_id of the incident to update\"),\n state: z\n .enum([\"1\", \"2\", \"3\", \"6\", \"7\"])\n .optional()\n .describe(\"New state (1=New, 2=In Progress, 3=On Hold, 6=Resolved, 7=Closed)\"),\n short_description: z.string().optional().describe(\"Updated short description\"),\n description: z.string().optional().describe(\"Updated description\"),\n urgency: z\n .enum([\"1\", \"2\", \"3\"])\n .optional()\n .describe(\"Updated urgency (1=High, 2=Medium, 3=Low)\"),\n impact: z\n .enum([\"1\", \"2\", \"3\"])\n .optional()\n .describe(\"Updated impact (1=High, 2=Medium, 3=Low)\"),\n work_notes: z.string().optional().describe(\"Add work notes to the incident\"),\n close_notes: z.string().optional().describe(\"Close notes (required when closing)\"),\n }),\n async execute(input) {\n if (!(await isServiceNowConnected())) {\n return {\n error: \"ServiceNow not connected\",\n action: \"Please connect ServiceNow via /api/auth/servicenow\",\n };\n }\n\n try {\n const client = getServiceNowClient();\n const { sys_id, ...updateData } = input;\n\n const cleanData = Object.fromEntries(\n Object.entries(updateData).filter(([, value]) => value !== undefined),\n );\n\n const incident = await client.updateIncident(sys_id, cleanData);\n\n return {\n success: true,\n number: incident.number,\n sys_id: incident.sys_id,\n state: incident.state,\n updated: incident.sys_updated_on,\n message: `Incident ${incident.number} updated successfully`,\n };\n } catch (error) {\n return {\n error: error instanceof Error ? error.message : \"Failed to update incident\",\n };\n }\n },\n});\n",
|
|
379
|
+
"tools/create-incident.ts": "import { z } from \"zod\";\nimport { getServiceNowClient } from \"../../lib/servicenow-client.ts\";\nimport { isServiceNowConnected } from \"../../lib/token-store.ts\";\n\nexport default defineTool({\n id: \"servicenow-create-incident\",\n description: \"Create a new incident in ServiceNow\",\n inputSchema: z.object({\n short_description: z.string().describe(\"Brief description of the incident\"),\n description: z.string().optional().describe(\"Detailed description of the incident\"),\n urgency: z\n .enum([\"1\", \"2\", \"3\"])\n .optional()\n .describe(\"Urgency level (1=High, 2=Medium, 3=Low)\"),\n impact: z\n .enum([\"1\", \"2\", \"3\"])\n .optional()\n .describe(\"Impact level (1=High, 2=Medium, 3=Low)\"),\n category: z.string().optional().describe(\"Incident category\"),\n subcategory: z.string().optional().describe(\"Incident subcategory\"),\n }),\n async execute(input) {\n if (!(await isServiceNowConnected())) {\n return {\n error: \"ServiceNow not connected\",\n action: \"Please connect ServiceNow via /api/auth/servicenow\",\n };\n }\n\n try {\n const client = getServiceNowClient();\n const incident = await client.createIncident(input);\n\n return {\n success: true,\n number: incident.number,\n sys_id: incident.sys_id,\n short_description: incident.short_description,\n state: incident.state,\n priority: incident.priority,\n message: `Incident ${incident.number} created successfully`,\n };\n } catch (error) {\n return {\n error: error instanceof Error ? error.message : \"Failed to create incident\",\n };\n }\n },\n});\n",
|
|
380
|
+
"tools/list-incidents.ts": "import { z } from \"zod\";\nimport { getServiceNowClient } from \"../../lib/servicenow-client.ts\";\nimport { isServiceNowConnected } from \"../../lib/token-store.ts\";\n\nconst stateMap: Record<string, string> = {\n new: \"1\",\n in_progress: \"2\",\n on_hold: \"3\",\n resolved: \"6\",\n closed: \"7\",\n};\n\nexport default defineTool({\n id: \"servicenow-list-incidents\",\n description:\n \"List incidents from ServiceNow with optional filters for state, priority, or search query\",\n inputSchema: z.object({\n limit: z.number().optional().describe(\"Maximum number of incidents to return (default: 20)\"),\n state: z\n .enum([\"new\", \"in_progress\", \"on_hold\", \"resolved\", \"closed\"])\n .optional()\n .describe(\"Filter by incident state\"),\n priority: z\n .enum([\"1\", \"2\", \"3\", \"4\", \"5\"])\n .optional()\n .describe(\"Filter by priority (1=Critical, 2=High, 3=Moderate, 4=Low, 5=Planning)\"),\n query: z.string().optional().describe(\"Search query for incident short description\"),\n }),\n async execute(input) {\n if (!(await isServiceNowConnected())) {\n return {\n error: \"ServiceNow not connected\",\n action: \"Please connect ServiceNow via /api/auth/servicenow\",\n };\n }\n\n try {\n const client = getServiceNowClient();\n const incidents = await client.listIncidents({\n limit: input.limit,\n state: input.state ? stateMap[input.state] : undefined,\n priority: input.priority,\n query: input.query,\n });\n\n return {\n count: incidents.length,\n incidents: incidents.map((inc) => ({\n number: inc.number,\n short_description: inc.short_description,\n state: inc.state,\n priority: inc.priority,\n urgency: inc.urgency,\n impact: inc.impact,\n assigned_to:\n typeof inc.assigned_to === \"object\" ? inc.assigned_to.display_value : inc.assigned_to,\n opened_at: inc.opened_at,\n sys_id: inc.sys_id,\n })),\n };\n } catch (error) {\n return {\n error: error instanceof Error ? error.message : \"Failed to list incidents\",\n };\n }\n },\n});\n",
|
|
381
|
+
"tools/get-incident.ts": "/**\n * Get ServiceNow Incident Tool\n */\n\nimport { z } from \"zod\";\nimport { getServiceNowClient } from \"../../lib/servicenow-client.ts\";\nimport { isServiceNowConnected } from \"../../lib/token-store.ts\";\n\nfunction getDisplayValue(value: unknown): unknown {\n if (value && typeof value === \"object\" && \"display_value\" in value) {\n return (value as { display_value: unknown }).display_value;\n }\n return value;\n}\n\nexport default defineTool({\n id: \"servicenow-get-incident\",\n description:\n \"Get details of a specific ServiceNow incident by number (e.g., INC0010001) or sys_id\",\n inputSchema: z.object({\n id: z.string().describe(\"Incident number (INC0010001) or sys_id\"),\n }),\n async execute(input) {\n if (!(await isServiceNowConnected())) {\n return {\n error: \"ServiceNow not connected\",\n action: \"Please connect ServiceNow via /api/auth/servicenow\",\n };\n }\n\n try {\n const client = getServiceNowClient();\n const incident = await client.getIncident(input.id);\n\n return {\n number: incident.number,\n sys_id: incident.sys_id,\n short_description: incident.short_description,\n description: incident.description,\n state: incident.state,\n priority: incident.priority,\n urgency: incident.urgency,\n impact: incident.impact,\n category: incident.category,\n subcategory: incident.subcategory,\n assigned_to: getDisplayValue(incident.assigned_to),\n caller_id: getDisplayValue(incident.caller_id),\n opened_at: incident.opened_at,\n resolved_at: incident.resolved_at,\n closed_at: incident.closed_at,\n created: incident.sys_created_on,\n updated: incident.sys_updated_on,\n };\n } catch (error) {\n return {\n error: error instanceof Error ? error.message : \"Failed to get incident\",\n };\n }\n },\n});\n",
|
|
382
|
+
"tools/search-knowledge.ts": "import { z } from \"zod\";\nimport { getServiceNowClient } from \"../../lib/servicenow-client.ts\";\nimport { isServiceNowConnected } from \"../../lib/token-store.ts\";\n\nexport default defineTool({\n id: \"servicenow-search-knowledge\",\n description: \"Search the ServiceNow knowledge base for articles matching a query\",\n inputSchema: z.object({\n query: z.string().describe(\"Search query for knowledge articles\"),\n limit: z.number().optional().describe(\"Maximum number of articles to return (default: 10)\"),\n }),\n async execute(input) {\n if (!(await isServiceNowConnected())) {\n return {\n error: \"ServiceNow not connected\",\n action: \"Please connect ServiceNow via /api/auth/servicenow\",\n };\n }\n\n try {\n const client = getServiceNowClient();\n const articles = await client.searchKnowledge(input.query, input.limit ?? 10);\n\n return {\n count: articles.length,\n articles: articles.map((article) => {\n const text = article.text ?? \"\";\n const summary = text.substring(0, 500) + (text.length > 500 ? \"...\" : \"\");\n\n return {\n number: article.number,\n title: article.short_description,\n category: article.kb_category,\n published: article.published,\n sys_id: article.sys_id,\n summary,\n };\n }),\n };\n } catch (error) {\n return {\n error: error instanceof Error ? error.message : \"Failed to search knowledge base\",\n };\n }\n },\n});\n",
|
|
383
|
+
"app/api/auth/servicenow/route.ts": "const getEnv = (name: string): string | undefined => {\n if (typeof Deno !== \"undefined\") {\n // @ts-ignore: Deno global\n return Deno.env.get(name);\n }\n\n // @ts-ignore: Node process\n return globalThis.process?.env?.[name];\n};\n\nexport function GET(request: Request): Response {\n const instance = getEnv(\"SERVICENOW_INSTANCE\");\n const clientId = getEnv(\"SERVICENOW_CLIENT_ID\");\n\n if (!instance || !clientId) {\n return Response.json(\n { error: \"ServiceNow OAuth not configured\" },\n { status: 500 },\n );\n }\n\n const instanceUrl = instance.includes(\"://\") ? instance : `https://${instance}`;\n\n const { protocol, host } = new URL(request.url);\n const baseUrl = getEnv(\"NEXT_PUBLIC_APP_URL\") ?? `${protocol}//${host}`;\n const redirectUri = `${baseUrl}/api/auth/servicenow/callback`;\n\n const authUrl = new URL(`${instanceUrl}/oauth_auth.do`);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUri);\n authUrl.searchParams.set(\"state\", crypto.randomUUID());\n\n return Response.redirect(authUrl.toString(), 302);\n}\n",
|
|
384
|
+
"app/api/auth/servicenow/callback/route.ts": "import { setServiceNowTokens } from \"../../../../../lib/token-store.ts\";\n\nfunction getEnv(name: string): string | undefined {\n if (typeof Deno !== \"undefined\") {\n // @ts-ignore: Deno global\n return Deno.env.get(name);\n }\n // @ts-ignore: Node process\n return globalThis.process?.env?.[name];\n}\n\nexport async function GET(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const code = url.searchParams.get(\"code\");\n const error = url.searchParams.get(\"error\");\n const errorDescription = url.searchParams.get(\"error_description\");\n\n const baseUrl = getEnv(\"NEXT_PUBLIC_APP_URL\") ?? `${url.protocol}//${url.host}`;\n\n if (error) {\n console.error(\"ServiceNow OAuth error:\", error, errorDescription);\n const description = encodeURIComponent(errorDescription ?? error);\n return Response.redirect(\n `${baseUrl}/?error=servicenow_oauth_failed&description=${description}`,\n 302,\n );\n }\n\n if (!code) return Response.redirect(`${baseUrl}/?error=no_code`, 302);\n\n const instance = getEnv(\"SERVICENOW_INSTANCE\");\n const clientId = getEnv(\"SERVICENOW_CLIENT_ID\");\n const clientSecret = getEnv(\"SERVICENOW_CLIENT_SECRET\");\n\n if (!instance || !clientId || !clientSecret) {\n return Response.redirect(`${baseUrl}/?error=servicenow_not_configured`, 302);\n }\n\n const instanceUrl = instance.includes(\"://\") ? instance : `https://${instance}`;\n const redirectUri = `${baseUrl}/api/auth/servicenow/callback`;\n\n try {\n const tokenResponse = await fetch(`${instanceUrl}/oauth_token.do`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n if (!tokenResponse.ok) {\n console.error(\"ServiceNow token exchange failed:\", await tokenResponse.text());\n return Response.redirect(`${baseUrl}/?error=token_exchange_failed`, 302);\n }\n\n const tokens = await tokenResponse.json();\n\n await setServiceNowTokens({\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: tokens.expires_in ? Date.now() + tokens.expires_in * 1000 : undefined,\n instanceUrl,\n });\n\n return Response.redirect(`${baseUrl}/?connected=servicenow`, 302);\n } catch (error) {\n console.error(\"ServiceNow OAuth error:\", err);\n return Response.redirect(`${baseUrl}/?error=servicenow_oauth_failed`, 302);\n }\n}\n",
|
|
385
|
+
".env.example": "# ServiceNow OAuth Configuration\n# Get these from your ServiceNow instance: System OAuth > Application Registry\nSERVICENOW_INSTANCE=your-instance.service-now.com\nSERVICENOW_CLIENT_ID=your_client_id\nSERVICENOW_CLIENT_SECRET=your_client_secret\n"
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
"integration:confluence": {
|
|
389
|
+
"files": {
|
|
390
|
+
"lib/confluence-client.ts": "import { getAccessToken, getCloudId } from \"./token-store.ts\";\n\nconst CONFLUENCE_API_BASE = \"https://api.atlassian.com/ex/confluence\";\n\ninterface ConfluenceResponse<T> {\n results: T[];\n size: number;\n start?: number;\n limit?: number;\n _links?: {\n next?: string;\n base?: string;\n };\n}\n\nexport interface ConfluenceSpace {\n id: string;\n key: string;\n name: string;\n type: string;\n status: string;\n _links: {\n webui: string;\n };\n}\n\nexport interface ConfluencePage {\n id: string;\n type: \"page\" | \"blogpost\";\n status: string;\n title: string;\n spaceId?: string;\n parentId?: string;\n version: {\n number: number;\n message?: string;\n };\n body?: {\n storage?: {\n value: string;\n representation: \"storage\";\n };\n view?: {\n value: string;\n representation: \"view\";\n };\n };\n _links: {\n webui: string;\n tinyui?: string;\n };\n}\n\nexport interface ConfluenceSearchResult {\n content: {\n id: string;\n type: string;\n status: string;\n title: string;\n space?: {\n id: string;\n key: string;\n name: string;\n };\n history?: {\n lastUpdated: {\n when: string;\n };\n };\n _links: {\n webui: string;\n };\n };\n excerpt?: string;\n url: string;\n resultGlobalContainer?: {\n title: string;\n };\n}\n\nasync function confluenceFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const [token, cloudId] = await Promise.all([getAccessToken(), getCloudId()]);\n\n if (!token || !cloudId) {\n throw new Error(\"Not authenticated with Confluence. Please connect your Atlassian account.\");\n }\n\n const url = `${CONFLUENCE_API_BASE}/${cloudId}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as { message?: string }));\n throw new Error(`Confluence API error: ${response.status} ${error.message ?? response.statusText}`);\n }\n\n return response.json();\n}\n\nexport async function listSpaces(options?: {\n limit?: number;\n type?: \"global\" | \"personal\";\n}): Promise<ConfluenceSpace[]> {\n const params = new URLSearchParams();\n\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n if (options?.type) params.set(\"type\", options.type);\n\n const query = params.toString();\n const endpoint = `/wiki/rest/api/space${query ? `?${query}` : \"\"}`;\n\n const response = await confluenceFetch<ConfluenceResponse<ConfluenceSpace>>(endpoint);\n return response.results ?? [];\n}\n\nexport async function searchContent(\n query: string,\n options?: {\n cql?: string;\n limit?: number;\n spaceKey?: string;\n },\n): Promise<ConfluenceSearchResult[]> {\n const params = new URLSearchParams();\n\n let cqlQuery = options?.cql ?? `title ~ \"${query}\" OR text ~ \"${query}\"`;\n if (options?.spaceKey) cqlQuery += ` AND space = \"${options.spaceKey}\"`;\n\n params.set(\"cql\", cqlQuery);\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n\n const response = await confluenceFetch<ConfluenceResponse<ConfluenceSearchResult>>(\n `/wiki/rest/api/search?${params.toString()}`,\n );\n\n return response.results ?? [];\n}\n\nexport function getPage(pageId: string, expand?: string[]): Promise<ConfluencePage> {\n const params = new URLSearchParams();\n\n if (expand?.length) params.set(\"expand\", expand.join(\",\"));\n\n const query = params.toString();\n const endpoint = `/wiki/rest/api/content/${pageId}${query ? `?${query}` : \"\"}`;\n\n return confluenceFetch<ConfluencePage>(endpoint);\n}\n\nexport function getPageContent(pageId: string): Promise<ConfluencePage> {\n return getPage(pageId, [\"body.storage\", \"body.view\", \"version\", \"space\"]);\n}\n\nexport function createPage(options: {\n spaceKey: string;\n title: string;\n content: string;\n parentId?: string;\n type?: \"page\" | \"blogpost\";\n}): Promise<ConfluencePage> {\n const body = {\n type: options.type ?? \"page\",\n title: options.title,\n space: { key: options.spaceKey },\n body: {\n storage: {\n value: options.content,\n representation: \"storage\" as const,\n },\n },\n ...(options.parentId ? { ancestors: [{ id: options.parentId }] } : {}),\n };\n\n return confluenceFetch<ConfluencePage>(\"/wiki/rest/api/content\", {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport async function updatePage(\n pageId: string,\n options: {\n title?: string;\n content?: string;\n version: number;\n versionMessage?: string;\n },\n): Promise<ConfluencePage> {\n await getPage(pageId, [\"version\"]);\n\n const body: Record<string, unknown> = {\n version: {\n number: options.version,\n message: options.versionMessage,\n },\n type: \"page\",\n };\n\n if (options.title) body.title = options.title;\n\n if (options.content) {\n body.body = {\n storage: {\n value: options.content,\n representation: \"storage\",\n },\n };\n }\n\n return confluenceFetch<ConfluencePage>(`/wiki/rest/api/content/${pageId}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n });\n}\n\nexport function extractPlainText(storageHtml: string): string {\n return storageHtml\n .replace(/<[^>]*>/g, \" \")\n .replace(/ /g, \" \")\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nexport function formatAsStorage(text: string): string {\n const paragraphs = text.split(\"\\n\\n\").filter((p) => p.trim());\n\n return paragraphs.map((p) => `<p>${escapeHtml(p.trim())}</p>`).join(\"\\n\");\n}\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n",
|
|
391
|
+
"tools/create-page.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createPage, formatAsStorage } from \"../../lib/confluence-client.ts\";\n\nexport default tool({\n id: \"create-page\",\n description:\n \"Create a new page in a Confluence space. Can optionally be created as a child of an existing page.\",\n inputSchema: z.object({\n spaceKey: z\n .string()\n .describe('The key of the space to create the page in (e.g., \"TEAM\", \"DEV\")'),\n title: z.string().describe(\"Title of the new page\"),\n content: z\n .string()\n .describe(\n \"Content for the page (can be plain text or Confluence storage format HTML)\",\n ),\n parentId: z\n .string()\n .optional()\n .describe(\"Optional ID of the parent page to create this as a child page\"),\n type: z\n .enum([\"page\", \"blogpost\"])\n .default(\"page\")\n .describe(\"Type of content to create\"),\n }),\n async execute({ spaceKey, title, content, parentId, type }) {\n const trimmedContent = content.trim();\n const storageContent = trimmedContent.startsWith(\"<\")\n ? content\n : formatAsStorage(content);\n\n const page = await createPage({\n spaceKey,\n title,\n content: storageContent,\n parentId,\n type,\n });\n\n return {\n id: page.id,\n title: page.title,\n type: page.type,\n url: page._links.webui,\n version: page.version.number,\n spaceId: page.spaceId,\n };\n },\n});\n",
|
|
392
|
+
"tools/update-page.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatAsStorage, getPage, updatePage } from \"../../lib/confluence-client.ts\";\n\nexport default tool({\n id: \"update-page\",\n description:\n \"Update the content or title of an existing Confluence page. Requires the current version number.\",\n inputSchema: z.object({\n pageId: z.string().describe(\"The ID of the page to update\"),\n title: z\n .string()\n .optional()\n .describe(\"New title for the page (leave empty to keep current title)\"),\n content: z\n .string()\n .optional()\n .describe(\"New content for the page (can be plain text or Confluence storage format HTML)\"),\n versionMessage: z\n .string()\n .optional()\n .describe(\"Optional message describing the changes made\"),\n }),\n async execute({ pageId, title, content, versionMessage }) {\n const currentPage = await getPage(pageId, [\"version\"]);\n\n const storageContent = content\n ? content.trim().startsWith(\"<\")\n ? content\n : formatAsStorage(content)\n : undefined;\n\n const updatedPage = await updatePage(pageId, {\n title,\n content: storageContent,\n version: currentPage.version.number + 1,\n versionMessage,\n });\n\n return {\n id: updatedPage.id,\n title: updatedPage.title,\n type: updatedPage.type,\n url: updatedPage._links.webui,\n version: updatedPage.version.number,\n versionMessage: updatedPage.version.message,\n };\n },\n});\n",
|
|
393
|
+
"tools/search-content.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { searchContent } from \"../../lib/confluence-client.ts\";\n\nexport default tool({\n id: \"search-content\",\n description:\n \"Search for pages and blog posts in Confluence. Returns matching content with titles, excerpts, and links.\",\n inputSchema: z.object({\n query: z.string().describe(\"Search query to find pages or blog posts\"),\n spaceKey: z\n .string()\n .optional()\n .describe(\"Optional space key to limit search to a specific space\"),\n limit: z\n .number()\n .min(1)\n .max(50)\n .default(10)\n .describe(\"Maximum number of results to return\"),\n }),\n async execute({ query, spaceKey, limit }) {\n const results = await searchContent(query, { spaceKey, limit });\n\n return results.map((result) => {\n const space = result.content.space;\n\n return {\n id: result.content.id,\n type: result.content.type,\n title: result.content.title,\n excerpt: result.excerpt,\n url: result.url,\n space: space\n ? {\n id: space.id,\n key: space.key,\n name: space.name,\n }\n : undefined,\n lastUpdated: result.content.history?.lastUpdated.when,\n };\n });\n },\n});\n",
|
|
394
|
+
"tools/list-spaces.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listSpaces } from \"../../lib/confluence-client.ts\";\n\nexport default tool({\n id: \"list-spaces\",\n description: \"List all accessible Confluence spaces. Returns space keys, names, and links.\",\n inputSchema: z.object({\n type: z\n .enum([\"global\", \"personal\", \"all\"])\n .default(\"all\")\n .describe(\"Type of spaces to list (global, personal, or all)\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(25)\n .describe(\"Maximum number of spaces to return\"),\n }),\n async execute({ type, limit }) {\n const spaces = await listSpaces({\n type: type === \"all\" ? undefined : type,\n limit,\n });\n\n return spaces.map((space) => ({\n id: space.id,\n key: space.key,\n name: space.name,\n type: space.type,\n status: space.status,\n url: space._links.webui,\n }));\n },\n});\n",
|
|
395
|
+
"tools/get-page.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { extractPlainText, getPageContent } from \"../../lib/confluence-client.ts\";\n\nexport default tool({\n id: \"get-page\",\n description:\n \"Get the content of a specific Confluence page. Returns the page title, content, and metadata.\",\n inputSchema: z.object({\n pageId: z.string().describe(\"The ID of the Confluence page to retrieve\"),\n }),\n async execute({ pageId }) {\n const page = await getPageContent(pageId);\n\n const content = page.body?.storage?.value ?? page.body?.view?.value ?? \"\";\n const plainTextContent = extractPlainText(content);\n\n return {\n id: page.id,\n type: page.type,\n title: page.title,\n content: plainTextContent,\n htmlContent: content,\n version: page.version.number,\n url: page._links.webui,\n spaceId: page.spaceId,\n parentId: page.parentId,\n };\n },\n});\n",
|
|
396
|
+
"app/api/auth/confluence/route.ts": "import { confluenceConfig, createOAuthInitHandler, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(confluenceConfig, { tokenStore: memoryTokenStore });\n",
|
|
397
|
+
"app/api/auth/confluence/callback/route.ts": "import { confluenceConfig, createOAuthCallbackHandler, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(confluenceConfig, { tokenStore: hybridTokenStore });\n",
|
|
398
|
+
".env.example": "# Atlassian OAuth credentials\n# Get these from: https://developer.atlassian.com/console/myapps/\nATLASSIAN_CLIENT_ID=your_client_id_here\nATLASSIAN_CLIENT_SECRET=your_client_secret_here\n"
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
"integration:monday": {
|
|
402
|
+
"files": {
|
|
403
|
+
"lib/monday-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst MONDAY_API_URL = \"https://api.monday.com/v2\";\n\ninterface MondayResponse<T> {\n data: T;\n account_id?: number;\n errors?: Array<{ message: string; locations?: unknown[] }>;\n}\n\ninterface MondayBoard {\n id: string;\n name: string;\n description?: string;\n board_kind: string;\n state: string;\n workspace_id?: string;\n created_at?: string;\n updated_at?: string;\n}\n\ninterface MondayItem {\n id: string;\n name: string;\n state?: string;\n board?: {\n id: string;\n name: string;\n };\n group?: {\n id: string;\n title: string;\n };\n column_values?: Array<{\n id: string;\n text?: string;\n title?: string;\n type?: string;\n value?: string;\n }>;\n created_at?: string;\n updated_at?: string;\n}\n\ninterface MondayUser {\n id: string;\n name: string;\n email: string;\n account: {\n id: string;\n name: string;\n };\n}\n\nasync function mondayFetch<T>(query: string, variables: Record<string, unknown> = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Monday.com. Please connect your account.\");\n }\n\n const response = await fetch(MONDAY_API_URL, {\n method: \"POST\",\n headers: {\n Authorization: token,\n \"Content-Type\": \"application/json\",\n \"API-Version\": \"2024-10\",\n },\n body: JSON.stringify({ query, variables }),\n });\n\n if (!response.ok) {\n throw new Error(`Monday.com API error: ${response.status} ${response.statusText}`);\n }\n\n const result: MondayResponse<T> = await response.json();\n const errorMessage = result.errors?.[0]?.message;\n if (errorMessage) {\n throw new Error(`Monday.com GraphQL error: ${errorMessage}`);\n }\n\n return result.data;\n}\n\nexport async function getMe(): Promise<MondayUser> {\n const query = `\n query {\n me {\n id\n name\n email\n account {\n id\n name\n }\n }\n }\n `;\n\n const data = await mondayFetch<{ me: MondayUser }>(query);\n return data.me;\n}\n\nexport async function listBoards(options?: {\n limit?: number;\n page?: number;\n workspaceIds?: string[];\n}): Promise<MondayBoard[]> {\n const limit = options?.limit ?? 50;\n const page = options?.page ?? 1;\n\n const workspaceIds = options?.workspaceIds?.length\n ? options.workspaceIds.map((id) => parseInt(id, 10))\n : null;\n\n const workspaceFilter = workspaceIds ? `, workspace_ids: [${workspaceIds.join(\",\")}]` : \"\";\n\n const query = `\n query {\n boards(limit: ${limit}, page: ${page}${workspaceFilter}) {\n id\n name\n description\n board_kind\n state\n workspace_id\n }\n }\n `;\n\n const data = await mondayFetch<{ boards: MondayBoard[] }>(query);\n return data.boards;\n}\n\nexport async function getBoard(boardId: string): Promise<MondayBoard> {\n const query = `\n query {\n boards(ids: [${boardId}]) {\n id\n name\n description\n board_kind\n state\n workspace_id\n }\n }\n `;\n\n const data = await mondayFetch<{ boards: MondayBoard[] }>(query);\n const board = data.boards?.[0];\n if (!board) {\n throw new Error(`Board with ID ${boardId} not found`);\n }\n return board;\n}\n\nexport async function listItems(options: {\n boardId: string;\n limit?: number;\n page?: number;\n}): Promise<MondayItem[]> {\n const limit = options.limit ?? 50;\n const page = options.page ?? 1;\n\n const query = `\n query {\n boards(ids: [${options.boardId}]) {\n items_page(limit: ${limit}, query_params: {page: ${page}}) {\n items {\n id\n name\n state\n board {\n id\n name\n }\n group {\n id\n title\n }\n column_values {\n id\n text\n title\n type\n value\n }\n created_at\n updated_at\n }\n }\n }\n }\n `;\n\n const data = await mondayFetch<{ boards: Array<{ items_page: { items: MondayItem[] } }> }>(query);\n return data.boards?.[0]?.items_page.items ?? [];\n}\n\nexport async function getItem(itemId: string): Promise<MondayItem> {\n const query = `\n query {\n items(ids: [${itemId}]) {\n id\n name\n state\n board {\n id\n name\n }\n group {\n id\n title\n }\n column_values {\n id\n text\n title\n type\n value\n }\n created_at\n updated_at\n }\n }\n `;\n\n const data = await mondayFetch<{ items: MondayItem[] }>(query);\n const item = data.items?.[0];\n if (!item) {\n throw new Error(`Item with ID ${itemId} not found`);\n }\n return item;\n}\n\nexport async function createItem(options: {\n boardId: string;\n groupId?: string;\n itemName: string;\n columnValues?: Record<string, unknown>;\n}): Promise<MondayItem> {\n const groupId = options.groupId ? `group_id: \"${options.groupId}\",` : \"\";\n const columnValues = options.columnValues\n ? `column_values: ${JSON.stringify(JSON.stringify(options.columnValues))},`\n : \"\";\n\n const query = `\n mutation {\n create_item(\n board_id: ${options.boardId},\n ${groupId}\n item_name: \"${options.itemName}\",\n ${columnValues}\n ) {\n id\n name\n state\n board {\n id\n name\n }\n group {\n id\n title\n }\n created_at\n }\n }\n `;\n\n const data = await mondayFetch<{ create_item: MondayItem }>(query);\n return data.create_item;\n}\n\nexport async function updateItem(\n itemId: string,\n updates: {\n columnValues?: Record<string, unknown>;\n name?: string;\n },\n): Promise<MondayItem> {\n if (updates.name) {\n const nameQuery = `\n mutation {\n change_simple_column_value(\n item_id: ${itemId},\n column_id: \"name\",\n value: \"${updates.name}\"\n ) {\n id\n name\n }\n }\n `;\n await mondayFetch<{ change_simple_column_value: MondayItem }>(nameQuery);\n }\n\n if (updates.columnValues) {\n const columnValuesStr = JSON.stringify(JSON.stringify(updates.columnValues));\n const query = `\n mutation {\n change_multiple_column_values(\n item_id: ${itemId},\n column_values: ${columnValuesStr}\n ) {\n id\n name\n state\n column_values {\n id\n text\n title\n type\n value\n }\n }\n }\n `;\n const data = await mondayFetch<{ change_multiple_column_values: MondayItem }>(query);\n return data.change_multiple_column_values;\n }\n\n return getItem(itemId);\n}\n",
|
|
404
|
+
"tools/update-item.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { updateItem } from \"../../lib/monday-client.ts\";\n\nexport default tool({\n id: \"update-item\",\n description: \"Update an existing Monday.com item. Can update the name and/or column values.\",\n inputSchema: z.object({\n itemId: z.string().describe(\"The ID of the item to update\"),\n name: z.string().optional().describe(\"New name/title for the item\"),\n columnValues: z\n .record(z.unknown())\n .optional()\n .describe(\n \"Column values to update as a key-value object. Keys are column IDs, values depend on column type.\",\n ),\n }),\n async execute({ itemId, name, columnValues }) {\n const item = await updateItem(itemId, { name, columnValues });\n\n return {\n success: true,\n item: {\n id: item.id,\n name: item.name,\n state: item.state,\n columnValues: item.column_values?.map(({ id, title, text, type }) => ({\n id,\n title,\n text,\n type,\n })),\n },\n };\n },\n});\n",
|
|
405
|
+
"tools/get-item.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getItem } from \"../../lib/monday-client.ts\";\n\nexport default tool({\n id: \"get-item\",\n description: \"Get details of a specific Monday.com item by its ID.\",\n inputSchema: z.object({\n itemId: z.string().describe(\"The ID of the item to retrieve\"),\n }),\n async execute({ itemId }) {\n const item = await getItem(itemId);\n\n return {\n id: item.id,\n name: item.name,\n state: item.state,\n board: item.board,\n group: item.group,\n columnValues: item.column_values?.map(({ id, title, text, type, value }) => ({\n id,\n title,\n text,\n type,\n value,\n })),\n createdAt: item.created_at,\n updatedAt: item.updated_at,\n };\n },\n});\n",
|
|
406
|
+
"tools/create-item.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createItem } from \"../../lib/monday-client.ts\";\n\nexport default tool({\n id: \"create-item\",\n description: \"Create a new item in a Monday.com board. Items are the rows in a board.\",\n inputSchema: z.object({\n boardId: z.string().describe(\"The ID of the board to create the item in\"),\n itemName: z.string().describe(\"The name/title of the item\"),\n groupId: z.string().optional().describe(\"Optional group ID within the board to add the item to\"),\n columnValues: z\n .record(z.unknown())\n .optional()\n .describe(\n \"Optional column values as a key-value object. Keys are column IDs, values depend on column type.\",\n ),\n }),\n async execute({ boardId, itemName, groupId, columnValues }) {\n const item = await createItem({ boardId, itemName, groupId, columnValues });\n\n return {\n success: true,\n item: {\n id: item.id,\n name: item.name,\n state: item.state,\n board: item.board,\n group: item.group,\n createdAt: item.created_at,\n },\n };\n },\n});\n",
|
|
407
|
+
"tools/list-boards.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listBoards } from \"../../lib/monday-client.ts\";\n\nexport default tool({\n id: \"list-boards\",\n description: \"List all boards in Monday.com. Can optionally filter by workspace IDs.\",\n inputSchema: z.object({\n workspaceIds: z\n .array(z.string())\n .optional()\n .describe(\"Optional list of workspace IDs to filter boards\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(50)\n .describe(\"Maximum number of boards to return\"),\n page: z.number().min(1).default(1).describe(\"Page number for pagination\"),\n }),\n async execute({ workspaceIds, limit, page }) {\n const boards = await listBoards({ workspaceIds, limit, page });\n\n return boards.map(\n ({ id, name, description, board_kind, state, workspace_id }) => ({\n id,\n name,\n description,\n boardKind: board_kind,\n state,\n workspaceId: workspace_id,\n }),\n );\n },\n});\n",
|
|
408
|
+
"tools/list-items.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listItems } from \"../../lib/monday-client.ts\";\n\nexport default tool({\n id: \"list-items\",\n description: \"List items from a Monday.com board. Items are the rows in a board.\",\n inputSchema: z.object({\n boardId: z.string().describe(\"The ID of the board to list items from\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(50)\n .describe(\"Maximum number of items to return\"),\n page: z.number().min(1).default(1).describe(\"Page number for pagination\"),\n }),\n async execute({ boardId, limit, page }) {\n const items = await listItems({ boardId, limit, page });\n\n return items.map((item) => ({\n id: item.id,\n name: item.name,\n state: item.state,\n board: item.board,\n group: item.group,\n columnValues: item.column_values?.map((col) => ({\n id: col.id,\n title: col.title,\n text: col.text,\n type: col.type,\n })),\n createdAt: item.created_at,\n updatedAt: item.updated_at,\n }));\n },\n});\n",
|
|
409
|
+
"app/api/auth/monday/route.ts": "import { createOAuthInitHandler, mondayConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(mondayConfig);\n",
|
|
410
|
+
"app/api/auth/monday/callback/route.ts": "/**\n * Monday.com OAuth Callback\n *\n * Handles the OAuth callback from Monday.com and stores the tokens.\n */\n\nimport { createOAuthCallbackHandler, memoryTokenStore, mondayConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState(state: string) {\n return memoryTokenStore.getState(state);\n },\n setState(state: { state: string; codeVerifier?: string; createdAt: number }) {\n return memoryTokenStore.setState(state);\n },\n clearState(state: string) {\n return memoryTokenStore.clearState(state);\n },\n};\n\nexport const GET = createOAuthCallbackHandler(mondayConfig, { tokenStore: hybridTokenStore });\n",
|
|
411
|
+
".env.example": "# Monday.com OAuth Configuration\n# Get your credentials from https://monday.com/developers/apps\nMONDAY_CLIENT_ID=your-client-id\nMONDAY_CLIENT_SECRET=your-client-secret\n"
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
"integration:dropbox": {
|
|
415
|
+
"files": {
|
|
416
|
+
"lib/dropbox-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst DROPBOX_API_URL = \"https://api.dropboxapi.com/2\";\nconst DROPBOX_CONTENT_URL = \"https://content.dropboxapi.com/2\";\n\nexport interface DropboxMetadata {\n \".tag\": \"file\" | \"folder\" | \"deleted\";\n name: string;\n path_lower?: string;\n path_display?: string;\n id: string;\n}\n\nexport interface DropboxFileMetadata extends DropboxMetadata {\n \".tag\": \"file\";\n client_modified: string;\n server_modified: string;\n rev: string;\n size: number;\n is_downloadable: boolean;\n content_hash?: string;\n}\n\nexport interface DropboxFolderMetadata extends DropboxMetadata {\n \".tag\": \"folder\";\n}\n\nexport interface ListFolderResult {\n entries: Array<DropboxFileMetadata | DropboxFolderMetadata>;\n cursor: string;\n has_more: boolean;\n}\n\nexport interface SearchResult {\n matches: Array<{\n match_type: {\n \".tag\": \"filename\" | \"content\" | \"both\";\n };\n metadata: {\n \".tag\": \"metadata\";\n metadata: DropboxFileMetadata | DropboxFolderMetadata;\n };\n }>;\n has_more: boolean;\n cursor?: string;\n}\n\nexport interface AccountInfo {\n account_id: string;\n name: {\n given_name: string;\n surname: string;\n familiar_name: string;\n display_name: string;\n };\n email: string;\n email_verified: boolean;\n disabled: boolean;\n country: string;\n locale: string;\n account_type: {\n \".tag\": \"basic\" | \"pro\" | \"business\";\n };\n}\n\nexport interface SpaceUsage {\n used: number;\n allocation: {\n \".tag\": \"individual\" | \"team\";\n allocated?: number;\n };\n}\n\nexport interface SharedLinkMetadata {\n url: string;\n id: string;\n name: string;\n path_lower?: string;\n link_permissions: {\n can_revoke: boolean;\n resolved_visibility?: {\n \".tag\": \"public\" | \"team_only\" | \"password\";\n };\n };\n}\n\nasync function requireAccessToken(): Promise<string> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Dropbox. Please connect your account.\");\n }\n return token;\n}\n\nasync function parseDropboxError(response: Response): Promise<unknown> {\n return response.json().catch(() => ({}));\n}\n\nfunction throwDropboxError(response: Response, error: unknown): never {\n throw new Error(\n `Dropbox API error: ${response.status} ${error?.error_summary || response.statusText}`,\n );\n}\n\nasync function dropboxRPC<T>(\n endpoint: string,\n body: Record<string, unknown> = {},\n): Promise<T> {\n const token = await requireAccessToken();\n\n const response = await fetch(`${DROPBOX_API_URL}${endpoint}`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const error = await parseDropboxError(response);\n throwDropboxError(response, error);\n }\n\n return response.json();\n}\n\nasync function dropboxContent<T>(\n endpoint: string,\n args: Record<string, unknown>,\n content?: string | Uint8Array,\n): Promise<T> {\n const token = await requireAccessToken();\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n \"Dropbox-API-Arg\": JSON.stringify(args),\n };\n\n const body = content;\n if (body != null) {\n headers[\"Content-Type\"] = \"application/octet-stream\";\n }\n\n const response = await fetch(`${DROPBOX_CONTENT_URL}${endpoint}`, {\n method: \"POST\",\n headers,\n body,\n });\n\n if (!response.ok) {\n const error = await parseDropboxError(response);\n throwDropboxError(response, error);\n }\n\n return response.json();\n}\n\nexport function getCurrentAccount(): Promise<AccountInfo> {\n return dropboxRPC<AccountInfo>(\"/users/get_current_account\");\n}\n\nexport function getSpaceUsage(): Promise<SpaceUsage> {\n return dropboxRPC<SpaceUsage>(\"/users/get_space_usage\");\n}\n\nexport function listFolder(\n path: string = \"\",\n options?: {\n recursive?: boolean;\n includeDeleted?: boolean;\n includeHasExplicitSharedMembers?: boolean;\n limit?: number;\n },\n): Promise<ListFolderResult> {\n return dropboxRPC<ListFolderResult>(\"/files/list_folder\", {\n path: path || \"\",\n recursive: options?.recursive ?? false,\n include_deleted: options?.includeDeleted ?? false,\n include_has_explicit_shared_members: options?.includeHasExplicitSharedMembers ?? false,\n limit: options?.limit ?? 100,\n });\n}\n\nexport function listFolderContinue(cursor: string): Promise<ListFolderResult> {\n return dropboxRPC<ListFolderResult>(\"/files/list_folder/continue\", { cursor });\n}\n\nexport function getMetadata(\n path: string,\n options?: {\n includeMediaInfo?: boolean;\n includeDeleted?: boolean;\n },\n): Promise<DropboxFileMetadata | DropboxFolderMetadata> {\n return dropboxRPC<DropboxFileMetadata | DropboxFolderMetadata>(\"/files/get_metadata\", {\n path,\n include_media_info: options?.includeMediaInfo ?? false,\n include_deleted: options?.includeDeleted ?? false,\n });\n}\n\nexport async function downloadFile(path: string): Promise<{\n content: string;\n metadata: DropboxFileMetadata;\n}> {\n const token = await requireAccessToken();\n\n const response = await fetch(`${DROPBOX_CONTENT_URL}/files/download`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Dropbox-API-Arg\": JSON.stringify({ path }),\n },\n });\n\n if (!response.ok) {\n const error = await parseDropboxError(response);\n throwDropboxError(response, error);\n }\n\n const content = await response.text();\n const metadataHeader = response.headers.get(\"Dropbox-API-Result\");\n const metadata = metadataHeader ? JSON.parse(metadataHeader) : {};\n\n return { content, metadata };\n}\n\nexport function uploadFile(\n path: string,\n content: string | Uint8Array,\n options?: {\n mode?: \"add\" | \"overwrite\" | \"update\";\n autorename?: boolean;\n mute?: boolean;\n },\n): Promise<DropboxFileMetadata> {\n return dropboxContent<DropboxFileMetadata>(\n \"/files/upload\",\n {\n path,\n mode: options?.mode ?? \"add\",\n autorename: options?.autorename ?? false,\n mute: options?.mute ?? false,\n },\n content,\n );\n}\n\nexport function deleteFile(\n path: string,\n): Promise<DropboxFileMetadata | DropboxFolderMetadata> {\n return dropboxRPC<{ metadata: DropboxFileMetadata | DropboxFolderMetadata }>(\n \"/files/delete_v2\",\n { path },\n ).then((result) => result.metadata);\n}\n\nexport function moveFile(\n fromPath: string,\n toPath: string,\n options?: {\n allowSharedFolder?: boolean;\n autorename?: boolean;\n allowOwnershipTransfer?: boolean;\n },\n): Promise<DropboxFileMetadata | DropboxFolderMetadata> {\n return dropboxRPC<{ metadata: DropboxFileMetadata | DropboxFolderMetadata }>(\n \"/files/move_v2\",\n {\n from_path: fromPath,\n to_path: toPath,\n allow_shared_folder: options?.allowSharedFolder ?? false,\n autorename: options?.autorename ?? false,\n allow_ownership_transfer: options?.allowOwnershipTransfer ?? false,\n },\n ).then((result) => result.metadata);\n}\n\nexport function copyFile(\n fromPath: string,\n toPath: string,\n options?: {\n allowSharedFolder?: boolean;\n autorename?: boolean;\n allowOwnershipTransfer?: boolean;\n },\n): Promise<DropboxFileMetadata | DropboxFolderMetadata> {\n return dropboxRPC<{ metadata: DropboxFileMetadata | DropboxFolderMetadata }>(\n \"/files/copy_v2\",\n {\n from_path: fromPath,\n to_path: toPath,\n allow_shared_folder: options?.allowSharedFolder ?? false,\n autorename: options?.autorename ?? false,\n allow_ownership_transfer: options?.allowOwnershipTransfer ?? false,\n },\n ).then((result) => result.metadata);\n}\n\nexport function createFolder(\n path: string,\n autorename?: boolean,\n): Promise<DropboxFolderMetadata> {\n return dropboxRPC<{ metadata: DropboxFolderMetadata }>(\"/files/create_folder_v2\", {\n path,\n autorename: autorename ?? false,\n }).then((result) => result.metadata);\n}\n\nexport function searchFiles(\n query: string,\n options?: {\n path?: string;\n maxResults?: number;\n fileCategories?: Array<\n | \"image\"\n | \"document\"\n | \"pdf\"\n | \"spreadsheet\"\n | \"presentation\"\n | \"audio\"\n | \"video\"\n | \"folder\"\n | \"paper\"\n | \"others\"\n >;\n fileExtensions?: string[];\n },\n): Promise<SearchResult> {\n return dropboxRPC<SearchResult>(\"/files/search_v2\", {\n query,\n options: {\n path: options?.path ?? \"\",\n max_results: options?.maxResults ?? 20,\n file_categories: options?.fileCategories,\n filename_only: false,\n },\n });\n}\n\nexport async function createSharedLink(\n path: string,\n settings?: {\n requestedVisibility?: \"public\" | \"team_only\" | \"password\";\n linkPassword?: string;\n expires?: string;\n },\n): Promise<SharedLinkMetadata> {\n try {\n return await dropboxRPC<SharedLinkMetadata>(\"/sharing/create_shared_link_with_settings\", {\n path,\n settings: settings ?? {},\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"shared_link_already_exists\")) {\n const links = await listSharedLinks(path);\n if (links.length > 0) return links[0];\n }\n throw error;\n }\n}\n\nexport async function listSharedLinks(path?: string): Promise<SharedLinkMetadata[]> {\n const result = await dropboxRPC<{ links: SharedLinkMetadata[] }>(\"/sharing/list_shared_links\", {\n path: path ?? \"\",\n });\n return result.links;\n}\n\nexport function formatFileSize(bytes: number): string {\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n let size = bytes;\n let unitIndex = 0;\n\n while (size >= 1024 && unitIndex < units.length - 1) {\n size /= 1024;\n unitIndex++;\n }\n\n return `${size.toFixed(2)} ${units[unitIndex]}`;\n}\n\nexport function isFile(metadata: DropboxMetadata): metadata is DropboxFileMetadata {\n return metadata[\".tag\"] === \"file\";\n}\n\nexport function isFolder(metadata: DropboxMetadata): metadata is DropboxFolderMetadata {\n return metadata[\".tag\"] === \"folder\";\n}\n",
|
|
417
|
+
"tools/upload-file.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatFileSize, uploadFile } from \"../../lib/dropbox-client.ts\";\n\nexport default tool({\n id: \"upload-file\",\n description:\n \"Upload or update a file in Dropbox. Can create new files or overwrite existing ones.\",\n inputSchema: z.object({\n path: z\n .string()\n .describe(\n 'Full path where the file should be saved in Dropbox (e.g., \"/Documents/notes.txt\")',\n ),\n content: z.string().describe(\"The content to write to the file\"),\n mode: z\n .enum([\"add\", \"overwrite\", \"update\"])\n .default(\"add\")\n .describe(\n 'Upload mode: \"add\" (fail if exists), \"overwrite\" (replace if exists), \"update\" (update specific revision)',\n ),\n autorename: z\n .boolean()\n .default(false)\n .describe(\"If true and file exists, automatically rename to avoid conflicts\"),\n }),\n async execute({ path, content, mode, autorename }) {\n if (!path.startsWith(\"/\")) {\n throw new Error('Path must start with \"/\" (e.g., \"/Documents/file.txt\")');\n }\n\n const result = await uploadFile(path, content, { mode, autorename, mute: false });\n const displayPath = result.path_display ?? result.path_lower ?? \"\";\n\n return {\n success: true,\n name: result.name,\n path: displayPath,\n id: result.id,\n size: result.size,\n sizeFormatted: formatFileSize(result.size),\n modified: result.server_modified,\n rev: result.rev,\n message:\n mode === \"add\"\n ? `File created successfully at ${result.path_display}`\n : `File updated successfully at ${result.path_display}`,\n };\n },\n});\n",
|
|
418
|
+
"tools/get-file.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { downloadFile, formatFileSize, getMetadata, isFile } from \"../../lib/dropbox-client.ts\";\n\nexport default tool({\n id: \"get-file\",\n description:\n \"Get file metadata and optionally download file content from Dropbox. Use this to read file information or retrieve file contents.\",\n inputSchema: z.object({\n path: z.string().describe('Path to the file in Dropbox (e.g., \"/Documents/file.txt\")'),\n includeContent: z\n .boolean()\n .default(false)\n .describe(\"Whether to download and return the file content (only works for text files and small files)\"),\n }),\n async execute({ path, includeContent }): Promise<Record<string, unknown>> {\n const metadata = await getMetadata(path);\n\n if (!isFile(metadata)) {\n throw new Error(`Path \"${path}\" is not a file, it's a ${metadata[\".tag\"]}`);\n }\n\n const result: Record<string, unknown> = {\n name: metadata.name,\n path: metadata.path_display ?? metadata.path_lower ?? \"\",\n id: metadata.id,\n size: metadata.size,\n sizeFormatted: formatFileSize(metadata.size),\n modified: metadata.server_modified,\n clientModified: metadata.client_modified,\n isDownloadable: metadata.is_downloadable,\n rev: metadata.rev,\n };\n\n if (!includeContent) return result;\n\n if (!metadata.is_downloadable) {\n throw new Error(`File \"${path}\" is not downloadable`);\n }\n\n if (metadata.size > 1024 * 1024) {\n throw new Error(\n `File is too large to download content (${formatFileSize(\n metadata.size,\n )}). Maximum size is 1MB. Use includeContent: false to get metadata only.`,\n );\n }\n\n try {\n const { content } = await downloadFile(path);\n result.content = content;\n result.contentLength = content.length;\n } catch (error) {\n result.contentError = error instanceof Error ? error.message : \"Failed to download content\";\n }\n\n return result;\n },\n});\n",
|
|
419
|
+
"tools/get-account.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport {\n formatFileSize,\n getCurrentAccount,\n getSpaceUsage,\n} from \"../../lib/dropbox-client.ts\";\n\nexport default tool({\n id: \"get-account\",\n description:\n \"Get current Dropbox account information including user details and storage usage.\",\n inputSchema: z.object({\n includeSpaceUsage: z\n .boolean()\n .default(true)\n .describe(\"Whether to include storage usage information\"),\n }),\n async execute({ includeSpaceUsage }): Promise<Record<string, unknown>> {\n const account = await getCurrentAccount();\n\n const result: Record<string, unknown> = {\n accountId: account.account_id,\n name: {\n displayName: account.name.display_name,\n givenName: account.name.given_name,\n surname: account.name.surname,\n familiarName: account.name.familiar_name,\n },\n email: account.email,\n emailVerified: account.email_verified,\n accountType: account.account_type[\".tag\"],\n country: account.country,\n locale: account.locale,\n disabled: account.disabled,\n };\n\n if (!includeSpaceUsage) return result;\n\n try {\n const spaceUsage = await getSpaceUsage();\n const used = spaceUsage.used;\n const allocated = spaceUsage.allocation.allocated ?? 0;\n\n result.storage = {\n used,\n usedFormatted: formatFileSize(used),\n allocated,\n allocatedFormatted: allocated ? formatFileSize(allocated) : \"N/A\",\n allocationType: spaceUsage.allocation[\".tag\"],\n percentUsed: allocated ? Math.round((used / allocated) * 100) : 0,\n available: allocated ? allocated - used : 0,\n availableFormatted: allocated ? formatFileSize(allocated - used) : \"N/A\",\n };\n } catch (error) {\n result.storageError =\n error instanceof Error ? error.message : \"Failed to get storage usage\";\n }\n\n return result;\n },\n});\n",
|
|
420
|
+
"tools/search-files.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatFileSize, isFile, searchFiles } from \"../../lib/dropbox-client.ts\";\n\nexport default tool({\n id: \"search-files\",\n description:\n \"Search for files and folders in Dropbox by name or content. Returns matching items with their paths and metadata.\",\n inputSchema: z.object({\n query: z.string().describe(\"Search query to find files or folders\"),\n path: z.string().optional().describe(\"Optional path to limit search to a specific folder\"),\n maxResults: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of results to return\"),\n fileCategories: z\n .array(\n z.enum([\n \"image\",\n \"document\",\n \"pdf\",\n \"spreadsheet\",\n \"presentation\",\n \"audio\",\n \"video\",\n \"folder\",\n \"paper\",\n \"others\",\n ]),\n )\n .optional()\n .describe(\"Filter by file categories\"),\n }),\n async execute({ query, path, maxResults, fileCategories }) {\n const result = await searchFiles(query, { path, maxResults, fileCategories });\n\n const matches = result.matches.map((match) => {\n const metadata = match.metadata.metadata;\n const baseInfo = {\n name: metadata.name,\n path: metadata.path_display ?? metadata.path_lower ?? \"\",\n id: metadata.id,\n type: metadata[\".tag\"],\n matchType: match.match_type[\".tag\"],\n };\n\n if (!isFile(metadata)) return baseInfo;\n\n return {\n ...baseInfo,\n size: metadata.size,\n sizeFormatted: formatFileSize(metadata.size),\n modified: metadata.server_modified,\n clientModified: metadata.client_modified,\n isDownloadable: metadata.is_downloadable,\n };\n });\n\n return {\n matches,\n count: matches.length,\n hasMore: result.has_more,\n query,\n };\n },\n});\n",
|
|
421
|
+
"tools/list-files.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatFileSize, isFile, listFolder } from \"../../lib/dropbox-client.ts\";\n\nexport default tool({\n id: \"list-files\",\n description:\n \"List files and folders in a Dropbox folder. Returns file/folder names, types, sizes, and modification dates.\",\n inputSchema: z.object({\n path: z\n .string()\n .default(\"\")\n .describe(\n 'Path to the folder to list (empty string for root, or \"/FolderName\" for specific folder)',\n ),\n recursive: z.boolean().default(false).describe(\"Whether to list files recursively in subfolders\"),\n limit: z.number().min(1).max(500).default(100).describe(\"Maximum number of items to return\"),\n }),\n async execute({ path, recursive, limit }) {\n const result = await listFolder(path, { recursive, limit });\n\n const items = result.entries.map((entry) => {\n const baseInfo = {\n name: entry.name,\n path: entry.path_display ?? entry.path_lower ?? \"\",\n id: entry.id,\n type: entry[\".tag\"],\n };\n\n if (!isFile(entry)) return baseInfo;\n\n return {\n ...baseInfo,\n size: entry.size,\n sizeFormatted: formatFileSize(entry.size),\n modified: entry.server_modified,\n clientModified: entry.client_modified,\n isDownloadable: entry.is_downloadable,\n rev: entry.rev,\n };\n });\n\n return {\n items,\n count: items.length,\n hasMore: result.has_more,\n cursor: result.cursor,\n };\n },\n});\n",
|
|
422
|
+
"app/api/auth/dropbox/route.ts": "import { createOAuthInitHandler, dropboxConfig, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(dropboxConfig, { tokenStore: memoryTokenStore });\n",
|
|
423
|
+
"app/api/auth/dropbox/callback/route.ts": "import { createOAuthCallbackHandler, dropboxConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(dropboxConfig, { tokenStore: hybridTokenStore });\n",
|
|
424
|
+
".env.example": "# Dropbox Integration Environment Variables\n\n# Dropbox App Key (Client ID)\n# Get this from https://www.dropbox.com/developers/apps\nDROPBOX_APP_KEY=your_app_key_here\n\n# Dropbox App Secret\n# Get this from https://www.dropbox.com/developers/apps\nDROPBOX_APP_SECRET=your_app_secret_here\n\n# Setup Instructions:\n# 1. Go to https://www.dropbox.com/developers/apps\n# 2. Create a new app or select an existing one\n# 3. Choose \"Scoped access\" as the API type\n# 4. Select \"Full Dropbox\" or \"App folder\" access\n# 5. Copy the App Key and App Secret\n# 6. Add the OAuth2 redirect URI: http://localhost:3000/api/auth/dropbox/callback\n# 7. Enable the following permissions in the Permissions tab:\n# - files.content.read\n# - files.content.write\n# - files.metadata.read\n# - files.metadata.write\n# - account_info.read\n# 8. Submit the app for production use if needed\n"
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
"integration:webex": {
|
|
428
|
+
"files": {
|
|
429
|
+
"lib/webex-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst WEBEX_BASE_URL = \"https://webexapis.com/v1\";\n\ninterface WebexMeeting {\n id: string;\n title: string;\n agenda?: string;\n start: string;\n end: string;\n timezone: string;\n hostEmail: string;\n hostDisplayName: string;\n webLink: string;\n sipAddress?: string;\n meetingNumber?: string;\n state: string;\n enabledAutoRecordMeeting?: boolean;\n allowAnyUserToBeCoHost?: boolean;\n}\n\ninterface WebexRoom {\n id: string;\n title: string;\n type: \"direct\" | \"group\";\n isLocked: boolean;\n lastActivity: string;\n creatorId: string;\n created: string;\n}\n\ninterface WebexMessage {\n id: string;\n roomId: string;\n roomType: string;\n text?: string;\n markdown?: string;\n personId: string;\n personEmail: string;\n created: string;\n}\n\ninterface WebexPerson {\n id: string;\n emails: string[];\n displayName: string;\n firstName?: string;\n lastName?: string;\n avatar?: string;\n orgId: string;\n created: string;\n lastActivity?: string;\n status?: string;\n type: string;\n}\n\nasync function webexFetch<T>(\n endpoint: string,\n options: RequestInit = {},\n): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Webex. Please connect your account.\");\n }\n\n const response = await fetch(`${WEBEX_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({}))) as {\n message?: string;\n };\n throw new Error(\n `Webex API error: ${response.status} ${error.message ?? response.statusText}`,\n );\n }\n\n return response.json() as Promise<T>;\n}\n\nexport async function getMe(): Promise<WebexPerson> {\n return webexFetch<WebexPerson>(\"/people/me\");\n}\n\n/**\n * List meetings for the authenticated user\n */\nexport async function listMeetings(options?: {\n max?: number;\n from?: string;\n to?: string;\n meetingType?: \"meeting\" | \"webinar\" | \"personalRoomMeeting\";\n state?: \"active\" | \"scheduled\" | \"ended\" | \"missed\" | \"inProgress\";\n}): Promise<WebexMeeting[]> {\n const params = new URLSearchParams();\n\n if (options?.max) params.set(\"max\", String(options.max));\n if (options?.from) params.set(\"from\", options.from);\n if (options?.to) params.set(\"to\", options.to);\n if (options?.meetingType) params.set(\"meetingType\", options.meetingType);\n if (options?.state) params.set(\"state\", options.state);\n\n const response = await webexFetch<{ items?: WebexMeeting[] }>(\n `/meetings?${params}`,\n );\n return response.items ?? [];\n}\n\n/**\n * Get details of a specific meeting\n */\nexport async function getMeeting(meetingId: string): Promise<WebexMeeting> {\n return webexFetch<WebexMeeting>(`/meetings/${meetingId}`);\n}\n\n/**\n * Create a new Webex meeting\n */\nexport async function createMeeting(options: {\n title: string;\n agenda?: string;\n start: string;\n end: string;\n timezone?: string;\n enabledAutoRecordMeeting?: boolean;\n allowAnyUserToBeCoHost?: boolean;\n invitees?: Array<{ email: string; displayName?: string; coHost?: boolean }>;\n}): Promise<WebexMeeting> {\n const body: Record<string, unknown> = {\n title: options.title,\n start: options.start,\n end: options.end,\n timezone: options.timezone ?? \"UTC\",\n ...(options.agenda ? { agenda: options.agenda } : {}),\n ...(options.enabledAutoRecordMeeting !== undefined\n ? { enabledAutoRecordMeeting: options.enabledAutoRecordMeeting }\n : {}),\n ...(options.allowAnyUserToBeCoHost !== undefined\n ? { allowAnyUserToBeCoHost: options.allowAnyUserToBeCoHost }\n : {}),\n ...(options.invitees?.length ? { invitees: options.invitees } : {}),\n };\n\n return webexFetch<WebexMeeting>(\"/meetings\", {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\n/**\n * Update an existing meeting\n */\nexport async function updateMeeting(\n meetingId: string,\n updates: {\n title?: string;\n agenda?: string;\n start?: string;\n end?: string;\n timezone?: string;\n enabledAutoRecordMeeting?: boolean;\n },\n): Promise<WebexMeeting> {\n return webexFetch<WebexMeeting>(`/meetings/${meetingId}`, {\n method: \"PATCH\",\n body: JSON.stringify(updates),\n });\n}\n\n/**\n * Delete a meeting\n */\nexport async function deleteMeeting(meetingId: string): Promise<void> {\n await webexFetch<void>(`/meetings/${meetingId}`, { method: \"DELETE\" });\n}\n\n/**\n * List Webex rooms (spaces)\n */\nexport async function listRooms(options?: {\n max?: number;\n type?: \"direct\" | \"group\";\n sortBy?: \"id\" | \"lastactivity\" | \"created\";\n}): Promise<WebexRoom[]> {\n const params = new URLSearchParams();\n\n if (options?.max) params.set(\"max\", String(options.max));\n if (options?.type) params.set(\"type\", options.type);\n if (options?.sortBy) params.set(\"sortBy\", options.sortBy);\n\n const response = await webexFetch<{ items?: WebexRoom[] }>(`/rooms?${params}`);\n return response.items ?? [];\n}\n\n/**\n * Get details of a specific room\n */\nexport async function getRoom(roomId: string): Promise<WebexRoom> {\n return webexFetch<WebexRoom>(`/rooms/${roomId}`);\n}\n\n/**\n * Create a new room\n */\nexport async function createRoom(options: {\n title: string;\n teamId?: string;\n}): Promise<WebexRoom> {\n return webexFetch<WebexRoom>(\"/rooms\", {\n method: \"POST\",\n body: JSON.stringify(options),\n });\n}\n\n/**\n * Send a message to a Webex room\n */\nexport async function sendMessage(options: {\n roomId?: string;\n toPersonId?: string;\n toPersonEmail?: string;\n text?: string;\n markdown?: string;\n files?: string[];\n}): Promise<WebexMessage> {\n if (!options.roomId && !options.toPersonId && !options.toPersonEmail) {\n throw new Error(\"Must specify roomId, toPersonId, or toPersonEmail\");\n }\n\n if (!options.text && !options.markdown && !options.files) {\n throw new Error(\"Must specify text, markdown, or files\");\n }\n\n return webexFetch<WebexMessage>(\"/messages\", {\n method: \"POST\",\n body: JSON.stringify(options),\n });\n}\n\n/**\n * List messages in a room\n */\nexport async function listMessages(options: {\n roomId: string;\n max?: number;\n before?: string;\n beforeMessage?: string;\n}): Promise<WebexMessage[]> {\n const params = new URLSearchParams({ roomId: options.roomId });\n\n if (options.max) params.set(\"max\", String(options.max));\n if (options.before) params.set(\"before\", options.before);\n if (options.beforeMessage) {\n params.set(\"beforeMessage\", options.beforeMessage);\n }\n\n const response = await webexFetch<{ items?: WebexMessage[] }>(\n `/messages?${params}`,\n );\n return response.items ?? [];\n}\n\n/**\n * Delete a message\n */\nexport async function deleteMessage(messageId: string): Promise<void> {\n await webexFetch<void>(`/messages/${messageId}`, { method: \"DELETE\" });\n}\n",
|
|
430
|
+
"tools/list-rooms.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listRooms } from \"../../lib/webex-client.ts\";\n\nexport default tool({\n id: \"list-rooms\",\n description:\n \"List Webex spaces/rooms. Can filter by type (direct messages or group spaces).\",\n inputSchema: z.object({\n max: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of rooms to return\"),\n type: z\n .enum([\"direct\", \"group\"])\n .optional()\n .describe(\n \"Filter by room type: 'direct' for 1:1 conversations, 'group' for team spaces\",\n ),\n sortBy: z\n .enum([\"id\", \"lastactivity\", \"created\"])\n .default(\"lastactivity\")\n .describe(\"Sort rooms by id, lastactivity, or created date\"),\n }),\n async execute({ max, type, sortBy }) {\n const rooms = await listRooms({ max, type, sortBy });\n\n return rooms.map(\n ({ id, title, type, isLocked, lastActivity, created }) => ({\n id,\n title,\n type,\n isLocked,\n lastActivity,\n created,\n }),\n );\n },\n});\n",
|
|
431
|
+
"tools/create-meeting.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createMeeting } from \"../../lib/webex-client.ts\";\n\nexport default tool({\n id: \"create-meeting\",\n description: \"Schedule a new Webex meeting with specified details.\",\n inputSchema: z.object({\n title: z.string().describe(\"Title of the meeting\"),\n agenda: z.string().optional().describe(\"Meeting agenda or description\"),\n start: z\n .string()\n .describe(\n \"Start date and time in ISO 8601 format (e.g., 2024-01-15T14:00:00Z)\",\n ),\n end: z\n .string()\n .describe(\n \"End date and time in ISO 8601 format (e.g., 2024-01-15T15:00:00Z)\",\n ),\n timezone: z\n .string()\n .default(\"UTC\")\n .describe(\"Timezone for the meeting (e.g., America/New_York, Europe/London)\"),\n enabledAutoRecordMeeting: z\n .boolean()\n .default(false)\n .describe(\"Automatically record the meeting\"),\n allowAnyUserToBeCoHost: z\n .boolean()\n .default(false)\n .describe(\"Allow any user to be a co-host\"),\n invitees: z\n .array(\n z.object({\n email: z.string().email().describe(\"Email address of the invitee\"),\n displayName: z.string().optional().describe(\"Display name of the invitee\"),\n coHost: z.boolean().default(false).describe(\"Make this invitee a co-host\"),\n }),\n )\n .optional()\n .describe(\"List of invitees to the meeting\"),\n }),\n async execute(input) {\n const meeting = await createMeeting(input);\n\n return {\n id: meeting.id,\n title: meeting.title,\n agenda: meeting.agenda,\n start: meeting.start,\n end: meeting.end,\n timezone: meeting.timezone,\n hostEmail: meeting.hostEmail,\n hostDisplayName: meeting.hostDisplayName,\n webLink: meeting.webLink,\n meetingNumber: meeting.meetingNumber,\n message: `Meeting \"${meeting.title}\" created successfully. Join URL: ${meeting.webLink}`,\n };\n },\n});\n",
|
|
432
|
+
"tools/send-message.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { sendMessage } from \"../../lib/webex-client.ts\";\n\nexport default tool({\n id: \"send-message\",\n description:\n \"Send a text or markdown message to a Webex room/space or directly to a person.\",\n inputSchema: z\n .object({\n roomId: z\n .string()\n .optional()\n .describe(\"Room ID to send the message to (use this OR toPersonEmail)\"),\n toPersonEmail: z\n .string()\n .email()\n .optional()\n .describe(\"Email address to send a direct message (use this OR roomId)\"),\n text: z\n .string()\n .optional()\n .describe(\"Plain text message content (use this OR markdown)\"),\n markdown: z\n .string()\n .optional()\n .describe(\"Markdown formatted message content (use this OR text)\"),\n })\n .refine((data) => data.roomId || data.toPersonEmail, {\n message: \"Must specify either roomId or toPersonEmail\",\n })\n .refine((data) => data.text || data.markdown, {\n message: \"Must specify either text or markdown\",\n }),\n async execute({ roomId, toPersonEmail, text, markdown }) {\n const message = await sendMessage({ roomId, toPersonEmail, text, markdown });\n\n return {\n id: message.id,\n roomId: message.roomId,\n text: message.text,\n markdown: message.markdown,\n personEmail: message.personEmail,\n created: message.created,\n message: `Message sent successfully to ${\n roomId ? `room ${roomId}` : toPersonEmail\n }`,\n };\n },\n});\n",
|
|
433
|
+
"tools/get-meeting.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getMeeting } from \"../../lib/webex-client.ts\";\n\nexport default tool({\n id: \"get-meeting\",\n description: \"Get detailed information about a specific Webex meeting by its ID.\",\n inputSchema: z.object({\n meetingId: z.string().describe(\"The unique ID of the meeting\"),\n }),\n async execute({ meetingId }) {\n const meeting = await getMeeting(meetingId);\n\n return { ...meeting };\n },\n});\n",
|
|
434
|
+
"tools/list-meetings.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listMeetings } from \"../../lib/webex-client.ts\";\n\nexport default tool({\n id: \"list-meetings\",\n description:\n \"List scheduled Webex meetings. Can filter by date range and meeting state.\",\n inputSchema: z.object({\n max: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of meetings to return\"),\n from: z\n .string()\n .optional()\n .describe(\"Start date in ISO 8601 format (e.g., 2024-01-01T00:00:00Z)\"),\n to: z\n .string()\n .optional()\n .describe(\"End date in ISO 8601 format (e.g., 2024-12-31T23:59:59Z)\"),\n state: z\n .enum([\"active\", \"scheduled\", \"ended\", \"missed\", \"inProgress\"])\n .optional()\n .describe(\"Filter by meeting state\"),\n }),\n async execute({ max, from, to, state }) {\n const meetings = await listMeetings({ max, from, to, state });\n\n return meetings.map(\n ({\n id,\n title,\n agenda,\n start,\n end,\n timezone,\n hostEmail,\n hostDisplayName,\n webLink,\n meetingNumber,\n state,\n }) => ({\n id,\n title,\n agenda,\n start,\n end,\n timezone,\n hostEmail,\n hostDisplayName,\n webLink,\n meetingNumber,\n state,\n }),\n );\n },\n});\n",
|
|
435
|
+
"app/api/auth/webex/route.ts": "import { createOAuthInitHandler, webexConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(webexConfig);\n",
|
|
436
|
+
"app/api/auth/webex/callback/route.ts": "import { createOAuthCallbackHandler, memoryTokenStore, webexConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) =>\n memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(webexConfig, { tokenStore: hybridTokenStore });\n",
|
|
437
|
+
".env.example": "# Webex OAuth Configuration\n# Get your credentials from https://developer.webex.com/my-apps\nWEBEX_CLIENT_ID=your-client-id\nWEBEX_CLIENT_SECRET=your-client-secret\n"
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
"integration:bitbucket": {
|
|
441
|
+
"files": {
|
|
442
|
+
"lib/bitbucket-client.ts": "import { getValidToken } from \"./oauth.ts\";\n\n// Helper for Cross-Platform environment access\nfunction getEnv(key: string): string | undefined {\n // @ts-ignore - Deno global\n if (typeof Deno !== \"undefined\") {\n // @ts-ignore - Deno global\n return Deno.env.get(key);\n }\n\n // @ts-ignore - process global\n if (typeof process !== \"undefined\" && process.env) {\n // @ts-ignore - process global\n return process.env[key];\n }\n\n return undefined;\n}\n\nconst BITBUCKET_API_BASE = \"https://api.bitbucket.org/2.0\";\n\nexport interface BitbucketUser {\n uuid: string;\n username: string;\n display_name: string;\n account_id: string;\n links: {\n avatar: { href: string };\n html: { href: string };\n };\n}\n\nexport interface Repository {\n uuid: string;\n name: string;\n full_name: string;\n description: string | null;\n is_private: boolean;\n mainbranch: { name: string } | null;\n language: string;\n size: number;\n updated_on: string;\n created_on: string;\n links: {\n html: { href: string };\n clone: Array<{ href: string; name: string }>;\n };\n owner: {\n username: string;\n display_name: string;\n };\n}\n\nexport interface PullRequest {\n id: number;\n title: string;\n description: string;\n state: \"OPEN\" | \"MERGED\" | \"DECLINED\" | \"SUPERSEDED\";\n author: {\n username: string;\n display_name: string;\n };\n created_on: string;\n updated_on: string;\n source: {\n branch: { name: string };\n repository: { full_name: string };\n };\n destination: {\n branch: { name: string };\n repository: { full_name: string };\n };\n links: {\n html: { href: string };\n diff: { href: string };\n };\n comment_count: number;\n task_count: number;\n}\n\nexport interface Issue {\n id: number;\n title: string;\n content: {\n raw: string;\n } | null;\n state: \"new\" | \"open\" | \"resolved\" | \"on hold\" | \"invalid\" | \"duplicate\" | \"wontfix\" | \"closed\";\n kind: \"bug\" | \"enhancement\" | \"proposal\" | \"task\";\n priority: \"trivial\" | \"minor\" | \"major\" | \"critical\" | \"blocker\";\n created_on: string;\n updated_on: string;\n reporter: {\n username: string;\n display_name: string;\n };\n assignee: {\n username: string;\n display_name: string;\n } | null;\n links: {\n html: { href: string };\n };\n}\n\n/**\n * Bitbucket OAuth provider configuration\n */\nexport const bitbucketOAuthProvider = {\n name: \"bitbucket\",\n authorizationUrl: \"https://bitbucket.org/site/oauth2/authorize\",\n tokenUrl: \"https://bitbucket.org/site/oauth2/access_token\",\n clientId: getEnv(\"BITBUCKET_CLIENT_ID\") ?? \"\",\n clientSecret: getEnv(\"BITBUCKET_CLIENT_SECRET\") ?? \"\",\n scopes: [\"repository\", \"pullrequest\", \"issue\", \"account\"],\n callbackPath: \"/api/auth/bitbucket/callback\",\n};\n\nfunction buildQuery(params: URLSearchParams): string {\n const query = params.toString();\n return query ? `?${query}` : \"\";\n}\n\n/**\n * Create a Bitbucket client for a specific user\n */\nexport function createBitbucketClient(userId: string) {\n async function getAccessToken(): Promise<string> {\n const token = await getValidToken(bitbucketOAuthProvider, userId, \"bitbucket\");\n if (!token) {\n throw new Error(\"Bitbucket not connected. Please connect your Bitbucket account first.\");\n }\n return token;\n }\n\n async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const accessToken = await getAccessToken();\n\n const response = await fetch(`${BITBUCKET_API_BASE}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Bitbucket API error: ${response.status} - ${error}`);\n }\n\n return response.json();\n }\n\n return {\n /**\n * Get authenticated user\n */\n getCurrentUser(): Promise<BitbucketUser> {\n return apiRequest(\"/user\");\n },\n\n /**\n * List user's repositories\n */\n async listRepositories(\n options: {\n role?: \"owner\" | \"contributor\" | \"member\";\n perPage?: number;\n } = {},\n ): Promise<Repository[]> {\n const params = new URLSearchParams();\n if (options.role) params.set(\"role\", options.role);\n if (options.perPage) params.set(\"pagelen\", String(options.perPage));\n\n const response = await apiRequest<{ values: Repository[] }>(\n `/repositories${buildQuery(params)}`,\n );\n return response.values;\n },\n\n /**\n * Get repository details\n */\n getRepository(workspace: string, repoSlug: string): Promise<Repository> {\n return apiRequest(`/repositories/${workspace}/${repoSlug}`);\n },\n\n /**\n * List pull requests for a repository\n */\n async listPullRequests(\n workspace: string,\n repoSlug: string,\n options: {\n state?: \"OPEN\" | \"MERGED\" | \"DECLINED\" | \"SUPERSEDED\";\n perPage?: number;\n } = {},\n ): Promise<PullRequest[]> {\n const params = new URLSearchParams();\n if (options.state) params.set(\"state\", options.state);\n if (options.perPage) params.set(\"pagelen\", String(options.perPage));\n\n const response = await apiRequest<{ values: PullRequest[] }>(\n `/repositories/${workspace}/${repoSlug}/pullrequests${buildQuery(params)}`,\n );\n return response.values;\n },\n\n /**\n * Get a single pull request\n */\n getPullRequest(\n workspace: string,\n repoSlug: string,\n pullRequestId: number,\n ): Promise<PullRequest> {\n return apiRequest(`/repositories/${workspace}/${repoSlug}/pullrequests/${pullRequestId}`);\n },\n\n /**\n * Create a pull request\n */\n createPullRequest(\n workspace: string,\n repoSlug: string,\n options: {\n title: string;\n description?: string;\n sourceBranch: string;\n destinationBranch: string;\n closeSourceBranch?: boolean;\n },\n ): Promise<PullRequest> {\n return apiRequest(`/repositories/${workspace}/${repoSlug}/pullrequests`, {\n method: \"POST\",\n body: JSON.stringify({\n title: options.title,\n description: options.description,\n source: { branch: { name: options.sourceBranch } },\n destination: { branch: { name: options.destinationBranch } },\n close_source_branch: options.closeSourceBranch,\n }),\n });\n },\n\n /**\n * List issues for a repository\n */\n async listIssues(\n workspace: string,\n repoSlug: string,\n options: {\n state?:\n | \"new\"\n | \"open\"\n | \"resolved\"\n | \"on hold\"\n | \"invalid\"\n | \"duplicate\"\n | \"wontfix\"\n | \"closed\";\n kind?: \"bug\" | \"enhancement\" | \"proposal\" | \"task\";\n priority?: \"trivial\" | \"minor\" | \"major\" | \"critical\" | \"blocker\";\n perPage?: number;\n } = {},\n ): Promise<Issue[]> {\n const params = new URLSearchParams();\n if (options.state) params.set(\"q\", `state=\"${options.state}\"`);\n if (options.kind) params.set(\"kind\", options.kind);\n if (options.priority) params.set(\"priority\", options.priority);\n if (options.perPage) params.set(\"pagelen\", String(options.perPage));\n\n const response = await apiRequest<{ values: Issue[] }>(\n `/repositories/${workspace}/${repoSlug}/issues${buildQuery(params)}`,\n );\n return response.values;\n },\n\n /**\n * Create an issue\n */\n createIssue(\n workspace: string,\n repoSlug: string,\n options: {\n title: string;\n description?: string;\n kind?: \"bug\" | \"enhancement\" | \"proposal\" | \"task\";\n priority?: \"trivial\" | \"minor\" | \"major\" | \"critical\" | \"blocker\";\n },\n ): Promise<Issue> {\n return apiRequest(`/repositories/${workspace}/${repoSlug}/issues`, {\n method: \"POST\",\n body: JSON.stringify({\n title: options.title,\n content: options.description ? { raw: options.description } : undefined,\n kind: options.kind ?? \"bug\",\n priority: options.priority ?? \"major\",\n }),\n });\n },\n };\n}\n\nexport type BitbucketClient = ReturnType<typeof createBitbucketClient>;\n",
|
|
443
|
+
"tools/list-pull-requests.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createBitbucketClient } from \"../../lib/bitbucket-client.ts\";\n\ntype PullRequest = {\n id: number;\n title: string;\n state: string;\n author: {\n username: string;\n display_name: string;\n };\n created_on: string;\n updated_on: string;\n source: {\n branch: { name: string };\n };\n destination: {\n branch: { name: string };\n };\n links: {\n html: { href: string };\n };\n comment_count: number;\n task_count: number;\n};\n\nexport default tool({\n id: \"list-pull-requests\",\n description: \"List pull requests for a Bitbucket repository\",\n inputSchema: z.object({\n workspace: z.string().describe(\"Workspace name or UUID\"),\n repoSlug: z.string().describe(\"Repository slug (e.g., 'my-repo')\"),\n state: z\n .enum([\"OPEN\", \"MERGED\", \"DECLINED\", \"SUPERSEDED\"])\n .default(\"OPEN\")\n .describe(\"State of pull requests to list\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(10)\n .describe(\"Maximum number of pull requests to return\"),\n }),\n execute: async ({ workspace, repoSlug, state, limit }, context) => {\n // Default to \"current-user\" for development; in production, always pass userId from session\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const bitbucket = createBitbucketClient(userId);\n const prs = await bitbucket.listPullRequests(workspace, repoSlug, {\n state,\n perPage: limit,\n });\n\n return {\n pullRequests: prs.map((pr: PullRequest) => ({\n id: pr.id,\n title: pr.title,\n state: pr.state,\n author: {\n username: pr.author.username,\n displayName: pr.author.display_name,\n },\n url: pr.links.html.href,\n sourceBranch: pr.source.branch.name,\n destinationBranch: pr.destination.branch.name,\n commentCount: pr.comment_count,\n taskCount: pr.task_count,\n createdOn: pr.created_on,\n updatedOn: pr.updated_on,\n })),\n count: prs.length,\n repository: `${workspace}/${repoSlug}`,\n message: `Found ${prs.length} ${state} pull request(s) in ${workspace}/${repoSlug}.`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Bitbucket not connected. Please connect your Bitbucket account.\",\n connectUrl: \"/api/auth/bitbucket\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
444
|
+
"tools/list-repositories.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createBitbucketClient } from \"../../lib/bitbucket-client.ts\";\n\ntype BitbucketRepo = {\n name: string;\n full_name: string;\n description: string | null;\n is_private: boolean;\n mainbranch: { name: string } | null;\n language: string;\n updated_on: string;\n created_on: string;\n links: { html: { href: string } };\n owner: { username: string; display_name: string };\n};\n\nexport default tool({\n id: \"list-repositories\",\n description: \"List Bitbucket repositories for the authenticated user\",\n inputSchema: z.object({\n role: z\n .enum([\"owner\", \"contributor\", \"member\"])\n .optional()\n .describe(\"Filter repositories by role\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of repositories to return\"),\n }),\n execute: async ({ role, limit }, context) => {\n const userId = (context?.userId as string | undefined) ?? \"current-user\";\n\n try {\n const bitbucket = createBitbucketClient(userId);\n const repos = await bitbucket.listRepositories({ role, perPage: limit });\n\n return {\n repositories: repos.map((repo: BitbucketRepo) => ({\n name: repo.name,\n fullName: repo.full_name,\n description: repo.description ?? null,\n isPrivate: repo.is_private,\n mainBranch: repo.mainbranch?.name ?? null,\n language: repo.language,\n url: repo.links.html.href,\n owner: {\n username: repo.owner.username,\n displayName: repo.owner.display_name,\n },\n updatedOn: repo.updated_on,\n createdOn: repo.created_on,\n })),\n count: repos.length,\n message: `Found ${repos.length} repository(s).`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Bitbucket not connected. Please connect your Bitbucket account.\",\n connectUrl: \"/api/auth/bitbucket\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
445
|
+
"tools/list-issues.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createBitbucketClient } from \"../../lib/bitbucket-client.ts\";\n\ntype BitbucketIssue = {\n id: number;\n title: string;\n state: string;\n kind: string;\n priority: string;\n created_on: string;\n updated_on: string;\n reporter: {\n username: string;\n display_name: string;\n };\n assignee: {\n username: string;\n display_name: string;\n } | null;\n links: {\n html: { href: string };\n };\n content: {\n raw: string;\n } | null;\n};\n\nexport default tool({\n id: \"list-issues\",\n description: \"List issues for a Bitbucket repository\",\n inputSchema: z.object({\n workspace: z.string().describe(\"Workspace name or UUID\"),\n repoSlug: z.string().describe(\"Repository slug (e.g., 'my-repo')\"),\n state: z\n .enum([\n \"new\",\n \"open\",\n \"resolved\",\n \"on hold\",\n \"invalid\",\n \"duplicate\",\n \"wontfix\",\n \"closed\",\n ])\n .optional()\n .describe(\"Filter by issue state\"),\n kind: z\n .enum([\"bug\", \"enhancement\", \"proposal\", \"task\"])\n .optional()\n .describe(\"Filter by issue kind\"),\n priority: z\n .enum([\"trivial\", \"minor\", \"major\", \"critical\", \"blocker\"])\n .optional()\n .describe(\"Filter by priority level\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of issues to return\"),\n }),\n execute: async ({ workspace, repoSlug, state, kind, priority, limit }, context) => {\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const bitbucket = createBitbucketClient(userId);\n const issues = await bitbucket.listIssues(workspace, repoSlug, {\n state,\n kind,\n priority,\n perPage: limit,\n });\n\n return {\n issues: issues.map((issue: BitbucketIssue) => ({\n id: issue.id,\n title: issue.title,\n state: issue.state,\n kind: issue.kind,\n priority: issue.priority,\n description: issue.content?.raw ?? null,\n reporter: {\n username: issue.reporter.username,\n displayName: issue.reporter.display_name,\n },\n assignee: issue.assignee\n ? {\n username: issue.assignee.username,\n displayName: issue.assignee.display_name,\n }\n : null,\n url: issue.links.html.href,\n createdOn: issue.created_on,\n updatedOn: issue.updated_on,\n })),\n count: issues.length,\n repository: `${workspace}/${repoSlug}`,\n filters: {\n state: state ?? \"all\",\n kind: kind ?? \"all\",\n priority: priority ?? \"all\",\n },\n message: `Found ${issues.length} issue(s) in ${workspace}/${repoSlug}.`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Bitbucket not connected. Please connect your Bitbucket account.\",\n connectUrl: \"/api/auth/bitbucket\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
446
|
+
"tools/create-pull-request.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createBitbucketClient } from \"../../lib/bitbucket-client.ts\";\n\nexport default tool({\n id: \"create-pull-request\",\n description: \"Create a new pull request in a Bitbucket repository\",\n inputSchema: z.object({\n workspace: z.string().describe(\"Workspace name or UUID\"),\n repoSlug: z.string().describe(\"Repository slug (e.g., 'my-repo')\"),\n title: z.string().min(1).describe(\"Pull request title\"),\n description: z\n .string()\n .optional()\n .describe(\"Pull request description (supports Markdown)\"),\n sourceBranch: z.string().describe(\"Source branch name\"),\n destinationBranch: z.string().describe(\"Destination branch name\"),\n closeSourceBranch: z\n .boolean()\n .optional()\n .default(false)\n .describe(\"Close source branch after merge\"),\n }),\n execute: async (\n {\n workspace,\n repoSlug,\n title,\n description,\n sourceBranch,\n destinationBranch,\n closeSourceBranch,\n },\n context,\n ) => {\n // Default to \"current-user\" for development; in production, always pass userId from session\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const bitbucket = createBitbucketClient(userId);\n const pr = await bitbucket.createPullRequest(workspace, repoSlug, {\n title,\n description,\n sourceBranch,\n destinationBranch,\n closeSourceBranch,\n });\n\n return {\n success: true,\n pullRequest: {\n id: pr.id,\n title: pr.title,\n url: pr.links.html.href,\n state: pr.state,\n sourceBranch: pr.source.branch.name,\n destinationBranch: pr.destination.branch.name,\n author: {\n username: pr.author.username,\n displayName: pr.author.display_name,\n },\n },\n message: `Pull request #${pr.id} created successfully in ${workspace}/${repoSlug}.`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Bitbucket not connected. Please connect your Bitbucket account.\",\n connectUrl: \"/api/auth/bitbucket\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
447
|
+
"app/api/auth/bitbucket/route.ts": "import { bitbucketConfig, createOAuthInitHandler, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(bitbucketConfig, { tokenStore: memoryTokenStore });\n",
|
|
448
|
+
"app/api/auth/bitbucket/callback/route.ts": "/**\n * Bitbucket OAuth Callback\n *\n * Handles the OAuth callback from Atlassian and stores the tokens.\n */\n\nimport { bitbucketConfig, createOAuthCallbackHandler, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string): Promise<unknown> {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ): Promise<void> {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string): Promise<void> {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState(state: string): unknown {\n return memoryTokenStore.getState(state);\n },\n setState(state: { state: string; codeVerifier?: string; createdAt: number }): unknown {\n return memoryTokenStore.setState(state);\n },\n clearState(state: string): unknown {\n return memoryTokenStore.clearState(state);\n },\n};\n\nexport const GET = createOAuthCallbackHandler(bitbucketConfig, { tokenStore: hybridTokenStore });\n",
|
|
449
|
+
".env.example": "# Bitbucket OAuth Configuration\n# Create a new OAuth consumer at: https://bitbucket.org/account/settings/app-passwords/\n# Or create an OAuth consumer at: https://bitbucket.org/{workspace}/workspace/settings/oauth-consumers/new\n# Set the callback URL to: http://localhost:3000/api/auth/bitbucket/callback\n# (Update the URL for production)\n# Required permissions: repository, pullrequest, issue, account\n\nBITBUCKET_CLIENT_ID=your_bitbucket_client_id\nBITBUCKET_CLIENT_SECRET=your_bitbucket_client_secret\n"
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
"integration:sheets": {
|
|
453
|
+
"files": {
|
|
454
|
+
"lib/sheets-client.ts": "/**\n * Google Sheets API Client\n *\n * Provides a type-safe interface to Google Sheets API operations.\n */\n\nimport { getValidToken } from \"./oauth.ts\";\n\n// Helper for Cross-Platform environment access\nfunction getEnv(key: string): string | undefined {\n // @ts-ignore - Deno global\n if (typeof Deno !== \"undefined\") return Deno.env.get(key);\n // @ts-ignore - process global\n if (typeof process !== \"undefined\" && process.env) return process.env[key];\n return undefined;\n}\n\nconst SHEETS_API_BASE = \"https://sheets.googleapis.com/v4\";\nconst DRIVE_API_BASE = \"https://www.googleapis.com/drive/v3\";\n\nexport interface Spreadsheet {\n spreadsheetId: string;\n properties: {\n title: string;\n locale: string;\n autoRecalc: string;\n timeZone: string;\n };\n sheets: Sheet[];\n spreadsheetUrl: string;\n}\n\nexport interface Sheet {\n properties: {\n sheetId: number;\n title: string;\n index: number;\n sheetType: \"GRID\" | \"OBJECT\";\n gridProperties?: {\n rowCount: number;\n columnCount: number;\n };\n };\n}\n\nexport interface SpreadsheetFile {\n id: string;\n name: string;\n mimeType: string;\n createdTime: string;\n modifiedTime: string;\n webViewLink: string;\n}\n\nexport interface CellData {\n values: unknown[][];\n range: string;\n}\n\nexport interface CreateSpreadsheetOptions {\n title: string;\n sheets?: Array<{\n title: string;\n rowCount?: number;\n columnCount?: number;\n }>;\n}\n\nexport interface WriteRangeOptions {\n spreadsheetId: string;\n range: string;\n values: unknown[][];\n valueInputOption?: \"RAW\" | \"USER_ENTERED\";\n}\n\n/**\n * Google Sheets OAuth provider configuration\n */\nexport const sheetsOAuthProvider = {\n name: \"sheets\",\n authorizationUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n tokenUrl: \"https://oauth2.googleapis.com/token\",\n clientId: getEnv(\"GOOGLE_CLIENT_ID\") ?? \"\",\n clientSecret: getEnv(\"GOOGLE_CLIENT_SECRET\") ?? \"\",\n scopes: [\n \"https://www.googleapis.com/auth/spreadsheets\",\n \"https://www.googleapis.com/auth/drive.readonly\",\n ],\n callbackPath: \"/api/auth/sheets/callback\",\n};\n\n/**\n * Create a Sheets client for a specific user\n */\nexport function createSheetsClient(userId: string): {\n listSpreadsheets(options?: {\n maxResults?: number;\n orderBy?: \"createdTime\" | \"modifiedTime\" | \"name\";\n }): Promise<SpreadsheetFile[]>;\n getSpreadsheet(spreadsheetId: string): Promise<Spreadsheet>;\n readRange(spreadsheetId: string, range: string): Promise<CellData>;\n readRanges(spreadsheetId: string, ranges: string[]): Promise<CellData[]>;\n writeRange(options: WriteRangeOptions): Promise<{\n updatedRange: string;\n updatedRows: number;\n updatedColumns: number;\n updatedCells: number;\n }>;\n appendRange(\n spreadsheetId: string,\n range: string,\n values: unknown[][],\n valueInputOption?: \"RAW\" | \"USER_ENTERED\",\n ): Promise<{\n updates: {\n updatedRange: string;\n updatedRows: number;\n updatedColumns: number;\n updatedCells: number;\n };\n }>;\n clearRange(spreadsheetId: string, range: string): Promise<{ clearedRange: string }>;\n createSpreadsheet(options: CreateSpreadsheetOptions): Promise<Spreadsheet>;\n addSheet(\n spreadsheetId: string,\n title: string,\n options?: { rowCount?: number; columnCount?: number },\n ): Promise<Sheet>;\n deleteSheet(spreadsheetId: string, sheetId: number): Promise<void>;\n} {\n async function getAccessToken(): Promise<string> {\n const token = await getValidToken(sheetsOAuthProvider, userId, \"sheets\");\n if (!token) {\n throw new Error(\"Google Sheets not connected. Please connect your Google account first.\");\n }\n return token;\n }\n\n async function apiRequest<T>(\n baseUrl: string,\n serviceName: \"Sheets\" | \"Drive\",\n endpoint: string,\n options: RequestInit = {},\n ): Promise<T> {\n const accessToken = await getAccessToken();\n\n const response = await fetch(`${baseUrl}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`${serviceName} API error: ${response.status} - ${error}`);\n }\n\n return response.json();\n }\n\n function sheetsApiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n return apiRequest<T>(SHEETS_API_BASE, \"Sheets\", endpoint, options);\n }\n\n function driveApiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n return apiRequest<T>(DRIVE_API_BASE, \"Drive\", endpoint, options);\n }\n\n return {\n /**\n * List spreadsheets from Google Drive\n */\n async listSpreadsheets(options: {\n maxResults?: number;\n orderBy?: \"createdTime\" | \"modifiedTime\" | \"name\";\n } = {}): Promise<SpreadsheetFile[]> {\n const params = new URLSearchParams({\n q: \"mimeType='application/vnd.google-apps.spreadsheet' and trashed=false\",\n fields: \"files(id,name,mimeType,createdTime,modifiedTime,webViewLink)\",\n pageSize: String(options.maxResults ?? 20),\n orderBy: `${options.orderBy ?? \"modifiedTime\"} desc`,\n });\n\n const result = await driveApiRequest<{ files?: SpreadsheetFile[] }>(`/files?${params.toString()}`);\n return result.files ?? [];\n },\n\n /**\n * Get spreadsheet metadata\n */\n getSpreadsheet(spreadsheetId: string): Promise<Spreadsheet> {\n return sheetsApiRequest<Spreadsheet>(`/spreadsheets/${spreadsheetId}`);\n },\n\n /**\n * Read data from a range\n */\n async readRange(spreadsheetId: string, range: string): Promise<CellData> {\n const result = await sheetsApiRequest<{ values?: unknown[][]; range: string }>(\n `/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}`,\n );\n\n return { values: result.values ?? [], range: result.range };\n },\n\n /**\n * Read multiple ranges at once\n */\n async readRanges(spreadsheetId: string, ranges: string[]): Promise<CellData[]> {\n const params = new URLSearchParams();\n for (const range of ranges) params.append(\"ranges\", range);\n\n const result = await sheetsApiRequest<{\n valueRanges: Array<{ values?: unknown[][]; range: string }>;\n }>(`/spreadsheets/${spreadsheetId}/values:batchGet?${params.toString()}`);\n\n return result.valueRanges.map((vr) => ({ values: vr.values ?? [], range: vr.range }));\n },\n\n /**\n * Write data to a range\n */\n writeRange(options: WriteRangeOptions): Promise<{\n updatedRange: string;\n updatedRows: number;\n updatedColumns: number;\n updatedCells: number;\n }> {\n return sheetsApiRequest(\n `/spreadsheets/${options.spreadsheetId}/values/${encodeURIComponent(options.range)}?valueInputOption=${\n options.valueInputOption ?? \"USER_ENTERED\"\n }`,\n {\n method: \"PUT\",\n body: JSON.stringify({ values: options.values }),\n },\n );\n },\n\n /**\n * Append data to a range\n */\n appendRange(\n spreadsheetId: string,\n range: string,\n values: unknown[][],\n valueInputOption: \"RAW\" | \"USER_ENTERED\" = \"USER_ENTERED\",\n ): Promise<{\n updates: {\n updatedRange: string;\n updatedRows: number;\n updatedColumns: number;\n updatedCells: number;\n };\n }> {\n return sheetsApiRequest(\n `/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}:append?valueInputOption=${valueInputOption}`,\n {\n method: \"POST\",\n body: JSON.stringify({ values }),\n },\n );\n },\n\n /**\n * Clear a range\n */\n clearRange(spreadsheetId: string, range: string): Promise<{ clearedRange: string }> {\n return sheetsApiRequest(`/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}:clear`, {\n method: \"POST\",\n });\n },\n\n /**\n * Create a new spreadsheet\n */\n createSpreadsheet(options: CreateSpreadsheetOptions): Promise<Spreadsheet> {\n const body: {\n properties: { title: string };\n sheets?: Array<{\n properties: {\n title: string;\n gridProperties?: { rowCount: number; columnCount: number };\n };\n }>;\n } = { properties: { title: options.title } };\n\n if (options.sheets?.length) {\n body.sheets = options.sheets.map((sheet) => ({\n properties: {\n title: sheet.title,\n gridProperties: {\n rowCount: sheet.rowCount ?? 1000,\n columnCount: sheet.columnCount ?? 26,\n },\n },\n }));\n }\n\n return sheetsApiRequest(\"/spreadsheets\", {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n },\n\n /**\n * Add a new sheet to an existing spreadsheet\n */\n async addSheet(\n spreadsheetId: string,\n title: string,\n options?: { rowCount?: number; columnCount?: number },\n ): Promise<Sheet> {\n const result = await sheetsApiRequest<{\n replies: Array<{ addSheet?: { properties: Sheet[\"properties\"] } }>;\n }>(`/spreadsheets/${spreadsheetId}:batchUpdate`, {\n method: \"POST\",\n body: JSON.stringify({\n requests: [\n {\n addSheet: {\n properties: {\n title,\n gridProperties: {\n rowCount: options?.rowCount ?? 1000,\n columnCount: options?.columnCount ?? 26,\n },\n },\n },\n },\n ],\n }),\n });\n\n const properties = result.replies[0]?.addSheet?.properties;\n if (!properties) throw new Error(\"Failed to add sheet\");\n\n return { properties };\n },\n\n /**\n * Delete a sheet from a spreadsheet\n */\n async deleteSheet(spreadsheetId: string, sheetId: number): Promise<void> {\n await sheetsApiRequest(`/spreadsheets/${spreadsheetId}:batchUpdate`, {\n method: \"POST\",\n body: JSON.stringify({\n requests: [{ deleteSheet: { sheetId } }],\n }),\n });\n },\n };\n}\n\nexport type SheetsClient = ReturnType<typeof createSheetsClient>;\n",
|
|
455
|
+
"tools/write-range.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createSheetsClient } from \"../../lib/sheets-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"write-range\",\n description:\n \"Write data to a Google Sheets range. Overwrites existing content in the specified range. Provide data as a 2D array where each inner array is a row.\",\n inputSchema: z.object({\n spreadsheetId: z.string().describe(\"The ID of the spreadsheet\"),\n range: z\n .string()\n .describe(\n \"Range in A1 notation where to write data (e.g., 'Sheet1!A1', 'Sheet1!A1:D5')\",\n ),\n values: z\n .array(z.array(z.any()))\n .describe(\n \"2D array of values to write. Each inner array represents a row. Example: [['Name', 'Age'], ['John', 30], ['Jane', 25]]\",\n ),\n valueInputOption: z\n .enum([\"RAW\", \"USER_ENTERED\"])\n .default(\"USER_ENTERED\")\n .describe(\n \"RAW: Values are stored as-is. USER_ENTERED: Values are parsed as if typed by user (formulas, numbers, dates)\",\n ),\n }),\n async execute({ spreadsheetId, range, values, valueInputOption }) {\n const client = createSheetsClient(DEFAULT_USER_ID);\n const { updatedRange, updatedRows, updatedColumns, updatedCells } =\n await client.writeRange({\n spreadsheetId,\n range,\n values,\n valueInputOption,\n });\n\n return { updatedRange, updatedRows, updatedColumns, updatedCells };\n },\n});\n",
|
|
456
|
+
"tools/get-spreadsheet.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createSheetsClient } from \"../../lib/sheets-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"get-spreadsheet\",\n description:\n \"Get metadata about a Google Sheets spreadsheet including all sheet names, properties, and structure. Use this to discover available sheets and their dimensions.\",\n inputSchema: z.object({\n spreadsheetId: z\n .string()\n .describe(\"The ID of the spreadsheet (from URL or list-spreadsheets)\"),\n }),\n async execute({ spreadsheetId }) {\n const client = createSheetsClient(DEFAULT_USER_ID);\n const spreadsheet = await client.getSpreadsheet(spreadsheetId);\n\n return {\n id: spreadsheet.spreadsheetId,\n title: spreadsheet.properties.title,\n url: spreadsheet.spreadsheetUrl,\n locale: spreadsheet.properties.locale,\n timeZone: spreadsheet.properties.timeZone,\n sheets: spreadsheet.sheets.map(({ properties }) => ({\n id: properties.sheetId,\n title: properties.title,\n index: properties.index,\n type: properties.sheetType,\n rowCount: properties.gridProperties?.rowCount,\n columnCount: properties.gridProperties?.columnCount,\n })),\n };\n },\n});\n",
|
|
457
|
+
"tools/list-spreadsheets.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createSheetsClient } from \"../../lib/sheets-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"list-spreadsheets\",\n description:\n \"List recent Google Sheets spreadsheets from Google Drive. Returns spreadsheet names, IDs, and metadata.\",\n inputSchema: z.object({\n maxResults: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of spreadsheets to return\"),\n orderBy: z\n .enum([\"createdTime\", \"modifiedTime\", \"name\"])\n .default(\"modifiedTime\")\n .describe(\"Sort order for results\"),\n }),\n async execute({ maxResults, orderBy }) {\n const client = createSheetsClient(DEFAULT_USER_ID);\n const spreadsheets = await client.listSpreadsheets({ maxResults, orderBy });\n\n return spreadsheets.map(({ id, name, webViewLink, createdTime, modifiedTime }) => ({\n id,\n name,\n url: webViewLink,\n createdTime,\n modifiedTime,\n }));\n },\n});\n",
|
|
458
|
+
"tools/create-spreadsheet.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createSheetsClient } from \"../../lib/sheets-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"create-spreadsheet\",\n description:\n \"Create a new Google Sheets spreadsheet with optional sheet configurations. Returns the new spreadsheet ID and URL.\",\n inputSchema: z.object({\n title: z.string().describe(\"Title of the new spreadsheet\"),\n sheets: z\n .array(\n z.object({\n title: z.string().describe(\"Name of the sheet/tab\"),\n rowCount: z\n .number()\n .min(1)\n .max(10000)\n .optional()\n .describe(\"Number of rows (default: 1000)\"),\n columnCount: z\n .number()\n .min(1)\n .max(26)\n .optional()\n .describe(\"Number of columns (default: 26)\"),\n }),\n )\n .optional()\n .describe(\n \"Optional array of sheet configurations. If not provided, a single default sheet is created.\",\n ),\n initialData: z\n .object({\n sheetTitle: z.string().describe(\"Name of the sheet to write data to\"),\n range: z\n .string()\n .describe(\"Range in A1 notation (e.g., 'A1', 'A1:D10')\"),\n values: z\n .array(z.array(z.any()))\n .describe(\n \"2D array of values to write. Example: [['Name', 'Age'], ['John', 30]]\",\n ),\n })\n .optional()\n .describe(\"Optional initial data to populate the spreadsheet\"),\n }),\n async execute({ title, sheets, initialData }) {\n const client = createSheetsClient(DEFAULT_USER_ID);\n\n const spreadsheet = await client.createSpreadsheet({ title, sheets });\n\n if (initialData) {\n await client.writeRange({\n spreadsheetId: spreadsheet.spreadsheetId,\n range: `${initialData.sheetTitle}!${initialData.range}`,\n values: initialData.values,\n valueInputOption: \"USER_ENTERED\",\n });\n }\n\n return {\n id: spreadsheet.spreadsheetId,\n title: spreadsheet.properties.title,\n url: spreadsheet.spreadsheetUrl,\n sheets: spreadsheet.sheets.map(({ properties }) => ({\n id: properties.sheetId,\n title: properties.title,\n index: properties.index,\n })),\n };\n },\n});\n",
|
|
459
|
+
"tools/read-range.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createSheetsClient } from \"../../lib/sheets-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"read-range\",\n description:\n \"Read cell data from a Google Sheets range. Returns a 2D array of values. Use A1 notation (e.g., 'Sheet1!A1:D10', 'A1:B', or just 'Sheet1' for entire sheet).\",\n inputSchema: z.object({\n spreadsheetId: z.string().describe(\"The ID of the spreadsheet\"),\n range: z\n .string()\n .describe(\n \"Range in A1 notation (e.g., 'Sheet1!A1:D10', 'A1:B5', or 'Sheet1' for entire sheet)\",\n ),\n }),\n async execute({ spreadsheetId, range }) {\n const client = createSheetsClient(DEFAULT_USER_ID);\n const result = await client.readRange(spreadsheetId, range);\n\n return {\n range: result.range,\n values: result.values,\n rowCount: result.values.length,\n columnCount: result.values[0]?.length ?? 0,\n };\n },\n});\n",
|
|
460
|
+
"app/api/auth/sheets/route.ts": "import { createOAuthInitHandler, memoryTokenStore, sheetsConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(sheetsConfig, { tokenStore: memoryTokenStore });\n",
|
|
461
|
+
"app/api/auth/sheets/callback/route.ts": "import { createOAuthCallbackHandler, memoryTokenStore, sheetsConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState(state: string) {\n return memoryTokenStore.getState(state);\n },\n setState(state: { state: string; codeVerifier?: string; createdAt: number }) {\n return memoryTokenStore.setState(state);\n },\n clearState(state: string) {\n return memoryTokenStore.clearState(state);\n },\n};\n\nexport const GET = createOAuthCallbackHandler(sheetsConfig, { tokenStore: hybridTokenStore });\n",
|
|
462
|
+
".env.example": "# Google Sheets Integration\n# Create OAuth credentials at https://console.cloud.google.com/apis/credentials\n# Make sure to enable:\n# - Google Sheets API: https://console.cloud.google.com/apis/library/sheets.googleapis.com\n# - Google Drive API: https://console.cloud.google.com/apis/library/drive.googleapis.com\n\nGOOGLE_CLIENT_ID=your_client_id_here\nGOOGLE_CLIENT_SECRET=your_client_secret_here\n"
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
"integration:linear": {
|
|
466
|
+
"files": {
|
|
467
|
+
"lib/linear-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst LINEAR_API_URL = \"https://api.linear.app/graphql\";\n\nexport interface LinearIssue {\n id: string;\n identifier: string;\n title: string;\n description?: string;\n priority: number;\n priorityLabel: string;\n state: {\n id: string;\n name: string;\n type: string;\n };\n assignee?: {\n id: string;\n name: string;\n email: string;\n };\n team: {\n id: string;\n name: string;\n key: string;\n };\n project?: {\n id: string;\n name: string;\n };\n labels: {\n nodes: Array<{\n id: string;\n name: string;\n color: string;\n }>;\n };\n createdAt: string;\n updatedAt: string;\n url: string;\n}\n\nexport interface LinearProject {\n id: string;\n name: string;\n description?: string;\n state: string;\n progress: number;\n url: string;\n lead?: {\n id: string;\n name: string;\n };\n teams: {\n nodes: Array<{\n id: string;\n name: string;\n key: string;\n }>;\n };\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface LinearTeam {\n id: string;\n name: string;\n key: string;\n}\n\nexport interface LinearWorkflowState {\n id: string;\n name: string;\n type: string;\n}\n\ninterface GraphQLResponse<T> {\n data?: T;\n errors?: Array<{\n message: string;\n path?: string[];\n }>;\n}\n\nasync function linearFetch<T>(query: string, variables?: Record<string, unknown>): Promise<T> {\n const token = await getAccessToken();\n if (!token) throw new Error(\"Not authenticated with Linear. Please connect your account.\");\n\n const response = await fetch(LINEAR_API_URL, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ query, variables }),\n });\n\n if (!response.ok) {\n throw new Error(`Linear API error: ${response.status} ${response.statusText}`);\n }\n\n const json: GraphQLResponse<T> = await response.json();\n\n const errorMessage = json.errors?.[0]?.message;\n if (errorMessage) throw new Error(`Linear GraphQL error: ${errorMessage}`);\n\n if (!json.data) throw new Error(\"Linear API returned no data\");\n\n return json.data;\n}\n\nexport async function searchIssues(\n query: string,\n options?: {\n limit?: number;\n includeArchived?: boolean;\n },\n): Promise<LinearIssue[]> {\n const gqlQuery = `\n query SearchIssues($query: String!, $first: Int, $includeArchived: Boolean) {\n issueSearch(query: $query, first: $first, includeArchived: $includeArchived) {\n nodes {\n id\n identifier\n title\n description\n priority\n priorityLabel\n state {\n id\n name\n type\n }\n assignee {\n id\n name\n email\n }\n team {\n id\n name\n key\n }\n project {\n id\n name\n }\n labels {\n nodes {\n id\n name\n color\n }\n }\n createdAt\n updatedAt\n url\n }\n }\n }\n `;\n\n const data = await linearFetch<{ issueSearch: { nodes: LinearIssue[] } }>(gqlQuery, {\n query,\n first: options?.limit ?? 10,\n includeArchived: options?.includeArchived ?? false,\n });\n\n return data.issueSearch.nodes;\n}\n\nexport async function getIssue(issueId: string): Promise<LinearIssue> {\n const query = `\n query GetIssue($id: String!) {\n issue(id: $id) {\n id\n identifier\n title\n description\n priority\n priorityLabel\n state {\n id\n name\n type\n }\n assignee {\n id\n name\n email\n }\n team {\n id\n name\n key\n }\n project {\n id\n name\n }\n labels {\n nodes {\n id\n name\n color\n }\n }\n createdAt\n updatedAt\n url\n }\n }\n `;\n\n const data = await linearFetch<{ issue: LinearIssue }>(query, { id: issueId });\n return data.issue;\n}\n\nexport async function createIssue(options: {\n teamId: string;\n title: string;\n description?: string;\n priority?: number;\n stateId?: string;\n assigneeId?: string;\n projectId?: string;\n labelIds?: string[];\n}): Promise<LinearIssue> {\n const mutation = `\n mutation CreateIssue($input: IssueCreateInput!) {\n issueCreate(input: $input) {\n success\n issue {\n id\n identifier\n title\n description\n priority\n priorityLabel\n state {\n id\n name\n type\n }\n assignee {\n id\n name\n email\n }\n team {\n id\n name\n key\n }\n project {\n id\n name\n }\n labels {\n nodes {\n id\n name\n color\n }\n }\n createdAt\n updatedAt\n url\n }\n }\n }\n `;\n\n const input: Record<string, unknown> = {\n teamId: options.teamId,\n title: options.title,\n };\n\n if (options.description) input.description = options.description;\n if (options.priority !== undefined) input.priority = options.priority;\n if (options.stateId) input.stateId = options.stateId;\n if (options.assigneeId) input.assigneeId = options.assigneeId;\n if (options.projectId) input.projectId = options.projectId;\n if (options.labelIds?.length) input.labelIds = options.labelIds;\n\n const data = await linearFetch<{ issueCreate: { success: boolean; issue: LinearIssue } }>(mutation, {\n input,\n });\n\n if (!data.issueCreate.success) throw new Error(\"Failed to create issue\");\n return data.issueCreate.issue;\n}\n\nexport async function updateIssue(\n issueId: string,\n options: {\n title?: string;\n description?: string;\n priority?: number;\n stateId?: string;\n assigneeId?: string;\n projectId?: string;\n labelIds?: string[];\n },\n): Promise<LinearIssue> {\n const mutation = `\n mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {\n issueUpdate(id: $id, input: $input) {\n success\n issue {\n id\n identifier\n title\n description\n priority\n priorityLabel\n state {\n id\n name\n type\n }\n assignee {\n id\n name\n email\n }\n team {\n id\n name\n key\n }\n project {\n id\n name\n }\n labels {\n nodes {\n id\n name\n color\n }\n }\n createdAt\n updatedAt\n url\n }\n }\n }\n `;\n\n const input: Record<string, unknown> = {};\n\n if (options.title) input.title = options.title;\n if (options.description !== undefined) input.description = options.description;\n if (options.priority !== undefined) input.priority = options.priority;\n if (options.stateId) input.stateId = options.stateId;\n if (options.assigneeId) input.assigneeId = options.assigneeId;\n if (options.projectId) input.projectId = options.projectId;\n if (options.labelIds) input.labelIds = options.labelIds;\n\n const data = await linearFetch<{ issueUpdate: { success: boolean; issue: LinearIssue } }>(mutation, {\n id: issueId,\n input,\n });\n\n if (!data.issueUpdate.success) throw new Error(\"Failed to update issue\");\n return data.issueUpdate.issue;\n}\n\nexport async function listProjects(options?: {\n limit?: number;\n includeArchived?: boolean;\n}): Promise<LinearProject[]> {\n const query = `\n query ListProjects($first: Int, $includeArchived: Boolean) {\n projects(first: $first, includeArchived: $includeArchived) {\n nodes {\n id\n name\n description\n state\n progress\n url\n lead {\n id\n name\n }\n teams {\n nodes {\n id\n name\n key\n }\n }\n createdAt\n updatedAt\n }\n }\n }\n `;\n\n const data = await linearFetch<{ projects: { nodes: LinearProject[] } }>(query, {\n first: options?.limit ?? 20,\n includeArchived: options?.includeArchived ?? false,\n });\n\n return data.projects.nodes;\n}\n\nexport async function getTeams(): Promise<LinearTeam[]> {\n const query = `\n query GetTeams {\n teams {\n nodes {\n id\n name\n key\n }\n }\n }\n `;\n\n const data = await linearFetch<{ teams: { nodes: LinearTeam[] } }>(query);\n return data.teams.nodes;\n}\n\nexport async function getWorkflowStates(teamId: string): Promise<LinearWorkflowState[]> {\n const query = `\n query GetWorkflowStates($teamId: String!) {\n team(id: $teamId) {\n states {\n nodes {\n id\n name\n type\n }\n }\n }\n }\n `;\n\n const data = await linearFetch<{ team: { states: { nodes: LinearWorkflowState[] } } }>(query, {\n teamId,\n });\n\n return data.team.states.nodes;\n}\n",
|
|
468
|
+
"tools/search-issues.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { searchIssues } from \"../../lib/linear-client.ts\";\n\nexport default tool({\n id: \"search-issues\",\n description:\n \"Search for Linear issues by title or description. Returns matching issues with their details including status, assignee, and team.\",\n inputSchema: z.object({\n query: z.string().describe(\"Search query to find issues (searches in title and description)\"),\n limit: z.number().min(1).max(50).default(10).describe(\"Maximum number of results to return\"),\n includeArchived: z\n .boolean()\n .default(false)\n .describe(\"Whether to include archived issues in results\"),\n }),\n async execute({ query, limit, includeArchived }) {\n const issues = await searchIssues(query, { limit, includeArchived });\n\n return issues.map((issue) => ({\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n description: issue.description,\n priority: issue.priorityLabel,\n status: issue.state.name,\n statusType: issue.state.type,\n assignee: issue.assignee\n ? { name: issue.assignee.name, email: issue.assignee.email }\n : null,\n team: { name: issue.team.name, key: issue.team.key },\n project: issue.project ? { name: issue.project.name } : null,\n labels: issue.labels.nodes.map((label) => ({\n name: label.name,\n color: label.color,\n })),\n url: issue.url,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n }));\n },\n});\n",
|
|
469
|
+
"tools/create-issue.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createIssue } from \"../../lib/linear-client.ts\";\n\nexport default tool({\n id: \"create-issue\",\n description:\n \"Create a new Linear issue in a specified team. You can optionally set priority, assign to someone, add to a project, and attach labels.\",\n inputSchema: z.object({\n teamId: z\n .string()\n .describe(\n \"The ID of the team to create the issue in. Use list-projects tool first if you need to find team IDs.\",\n ),\n title: z.string().describe(\"Title of the issue\"),\n description: z\n .string()\n .optional()\n .describe(\"Detailed description of the issue (supports markdown)\"),\n priority: z\n .number()\n .min(0)\n .max(4)\n .optional()\n .describe(\"Priority level: 0=No priority, 1=Urgent, 2=High, 3=Medium, 4=Low\"),\n stateId: z\n .string()\n .optional()\n .describe('Workflow state ID (e.g., \"Todo\", \"In Progress\", \"Done\")'),\n assigneeId: z.string().optional().describe(\"User ID to assign the issue to\"),\n projectId: z.string().optional().describe(\"Project ID to add the issue to\"),\n labelIds: z\n .array(z.string())\n .optional()\n .describe(\"Array of label IDs to attach to the issue\"),\n }),\n async execute(\n { teamId, title, description, priority, stateId, assigneeId, projectId, labelIds },\n ) {\n const issue = await createIssue({\n teamId,\n title,\n description,\n priority,\n stateId,\n assigneeId,\n projectId,\n labelIds,\n });\n\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n description: issue.description,\n priority: issue.priorityLabel,\n status: issue.state.name,\n assignee: issue.assignee\n ? { name: issue.assignee.name, email: issue.assignee.email }\n : null,\n team: {\n name: issue.team.name,\n key: issue.team.key,\n },\n project: issue.project ? { name: issue.project.name } : null,\n labels: issue.labels.nodes.map((label) => ({\n name: label.name,\n color: label.color,\n })),\n url: issue.url,\n createdAt: issue.createdAt,\n };\n },\n});\n",
|
|
470
|
+
"tools/update-issue.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { updateIssue } from \"../../lib/linear-client.ts\";\n\nexport default tool({\n id: \"update-issue\",\n description:\n \"Update an existing Linear issue. You can change the title, description, status, priority, assignee, project, or labels.\",\n inputSchema: z.object({\n issueId: z.string().describe(\"The ID of the issue to update\"),\n title: z.string().optional().describe(\"New title for the issue\"),\n description: z\n .string()\n .optional()\n .describe(\"New description for the issue (supports markdown)\"),\n priority: z\n .number()\n .min(0)\n .max(4)\n .optional()\n .describe(\"New priority level: 0=No priority, 1=Urgent, 2=High, 3=Medium, 4=Low\"),\n stateId: z.string().optional().describe(\"New workflow state ID to move the issue to\"),\n assigneeId: z\n .string()\n .optional()\n .describe(\"User ID to assign the issue to (or null to unassign)\"),\n projectId: z.string().optional().describe(\"Project ID to move the issue to\"),\n labelIds: z\n .array(z.string())\n .optional()\n .describe(\"New array of label IDs (replaces existing labels)\"),\n }),\n async execute(\n { issueId, title, description, priority, stateId, assigneeId, projectId, labelIds },\n ) {\n const issue = await updateIssue(issueId, {\n title,\n description,\n priority,\n stateId,\n assigneeId,\n projectId,\n labelIds,\n });\n\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n description: issue.description,\n priority: issue.priorityLabel,\n status: issue.state.name,\n statusType: issue.state.type,\n assignee: issue.assignee\n ? { name: issue.assignee.name, email: issue.assignee.email }\n : null,\n team: {\n name: issue.team.name,\n key: issue.team.key,\n },\n project: issue.project ? { name: issue.project.name } : null,\n labels: issue.labels.nodes.map((label) => ({\n name: label.name,\n color: label.color,\n })),\n url: issue.url,\n updatedAt: issue.updatedAt,\n };\n },\n});\n",
|
|
471
|
+
"tools/list-projects.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listProjects } from \"../../lib/linear-client.ts\";\n\nexport default tool({\n id: \"list-projects\",\n description:\n \"List all projects in the Linear workspace. Returns project details including name, state, progress, and associated teams.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of projects to return\"),\n includeArchived: z\n .boolean()\n .default(false)\n .describe(\"Whether to include archived projects in results\"),\n }),\n async execute({ limit, includeArchived }) {\n const projects = await listProjects({ limit, includeArchived });\n\n return projects.map((project) => {\n const lead = project.lead\n ? { id: project.lead.id, name: project.lead.name }\n : null;\n\n return {\n id: project.id,\n name: project.name,\n description: project.description,\n state: project.state,\n progress: Math.round(project.progress * 100), // Convert to percentage\n url: project.url,\n lead,\n teams: project.teams.nodes.map((team) => ({\n id: team.id,\n name: team.name,\n key: team.key,\n })),\n createdAt: project.createdAt,\n updatedAt: project.updatedAt,\n };\n });\n },\n});\n",
|
|
472
|
+
"tools/get-issue.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getIssue } from \"../../lib/linear-client.ts\";\n\nexport default tool({\n id: \"get-issue\",\n description:\n \"Get detailed information about a specific Linear issue by its ID or identifier (e.g., ENG-123). Returns complete issue details including description, status, assignee, labels, and project.\",\n inputSchema: z.object({\n issueId: z\n .string()\n .describe(\n 'The ID or identifier of the issue (e.g., \"ENG-123\" or full UUID)',\n ),\n }),\n async execute({ issueId }) {\n const issue = await getIssue(issueId);\n\n const assignee = issue.assignee\n ? {\n id: issue.assignee.id,\n name: issue.assignee.name,\n email: issue.assignee.email,\n }\n : null;\n\n const project = issue.project\n ? {\n id: issue.project.id,\n name: issue.project.name,\n }\n : null;\n\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n description: issue.description,\n priority: issue.priorityLabel,\n priorityNumber: issue.priority,\n status: issue.state.name,\n statusType: issue.state.type,\n stateId: issue.state.id,\n assignee,\n team: {\n id: issue.team.id,\n name: issue.team.name,\n key: issue.team.key,\n },\n project,\n labels: issue.labels.nodes.map(({ id, name, color }) => ({\n id,\n name,\n color,\n })),\n url: issue.url,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n },\n});\n",
|
|
473
|
+
"app/api/auth/linear/route.ts": "import { createOAuthInitHandler, linearConfig, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(linearConfig, { tokenStore: memoryTokenStore });\n",
|
|
474
|
+
"app/api/auth/linear/callback/route.ts": "/**\n * Linear OAuth Callback\n *\n * Handles the OAuth callback from Linear and stores the tokens.\n */\n\nimport { createOAuthCallbackHandler, linearConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\n// Hybrid adapter: uses framework's memoryTokenStore for state (PKCE),\n// but user's tokenStore for actual token storage\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState(state: string) {\n return memoryTokenStore.getState(state);\n },\n setState(state: { state: string; codeVerifier?: string; createdAt: number }) {\n return memoryTokenStore.setState(state);\n },\n clearState(state: string) {\n return memoryTokenStore.clearState(state);\n },\n};\n\nexport const GET = createOAuthCallbackHandler(linearConfig, { tokenStore: hybridTokenStore });\n",
|
|
475
|
+
".env.example": "# Linear Integration\n# Create an OAuth application at https://linear.app/settings/api\n# Set the callback URL to: http://localhost:3000/api/auth/linear/callback (or your production URL)\n\nLINEAR_CLIENT_ID=your_client_id_here\nLINEAR_CLIENT_SECRET=your_client_secret_here\n"
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
"integration:onedrive": {
|
|
479
|
+
"files": {
|
|
480
|
+
"lib/onedrive-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst GRAPH_API_URL = \"https://graph.microsoft.com/v1.0\";\n\n// OneDrive API Types (Microsoft Graph API)\nexport interface DriveItem {\n id: string;\n name: string;\n size?: number;\n createdDateTime: string;\n lastModifiedDateTime: string;\n webUrl: string;\n parentReference?: {\n driveId: string;\n id: string;\n path: string;\n };\n file?: {\n mimeType: string;\n hashes?: {\n quickXorHash?: string;\n sha1Hash?: string;\n sha256Hash?: string;\n };\n };\n folder?: {\n childCount: number;\n };\n \"@microsoft.graph.downloadUrl\"?: string;\n}\n\nexport interface FileMetadata {\n id: string;\n name: string;\n size: number;\n mimeType: string;\n createdDateTime: string;\n lastModifiedDateTime: string;\n webUrl: string;\n downloadUrl?: string;\n}\n\nexport interface FolderMetadata {\n id: string;\n name: string;\n childCount: number;\n createdDateTime: string;\n lastModifiedDateTime: string;\n webUrl: string;\n}\n\nexport interface ListFilesResult {\n value: DriveItem[];\n \"@odata.nextLink\"?: string;\n}\n\nexport interface SearchResult {\n value: DriveItem[];\n \"@odata.nextLink\"?: string;\n}\n\nasync function getTokenOrThrow(): Promise<string> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with OneDrive. Please connect your account.\");\n }\n return token;\n}\n\n// Helper function for OneDrive API calls\nasync function onedriveFetch<T>(\n endpoint: string,\n options: RequestInit = {},\n): Promise<T> {\n const token = await getTokenOrThrow();\n const url = endpoint.startsWith(\"http\") ? endpoint : `${GRAPH_API_URL}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(\n `OneDrive API error: ${response.status} ${error.error?.message ?? response.statusText}`,\n );\n }\n\n return response.json();\n}\n\n// File and Folder Operations\n\nexport function listFiles(\n folderId: string = \"root\",\n options?: {\n orderBy?: string;\n top?: number;\n select?: string[];\n },\n): Promise<ListFilesResult> {\n const params = new URLSearchParams();\n\n if (options?.orderBy) params.set(\"$orderby\", options.orderBy);\n if (options?.top) params.set(\"$top\", options.top.toString());\n if (options?.select) params.set(\"$select\", options.select.join(\",\"));\n\n const queryString = params.toString();\n const endpoint = `/me/drive/items/${folderId}/children${queryString ? `?${queryString}` : \"\"}`;\n\n return onedriveFetch<ListFilesResult>(endpoint);\n}\n\nexport function getFile(itemId: string): Promise<DriveItem> {\n return onedriveFetch<DriveItem>(`/me/drive/items/${itemId}`);\n}\n\nexport async function downloadFile(itemId: string): Promise<{\n content: string;\n metadata: FileMetadata;\n}> {\n const item = await getFile(itemId);\n\n if (!item.file) throw new Error(\"Item is not a file\");\n\n const downloadUrl = item[\"@microsoft.graph.downloadUrl\"];\n if (!downloadUrl) throw new Error(\"Download URL not available\");\n\n const response = await fetch(downloadUrl);\n if (!response.ok) throw new Error(`Failed to download file: ${response.statusText}`);\n\n const content = await response.text();\n\n return {\n content,\n metadata: {\n id: item.id,\n name: item.name,\n size: item.size ?? 0,\n mimeType: item.file.mimeType,\n createdDateTime: item.createdDateTime,\n lastModifiedDateTime: item.lastModifiedDateTime,\n webUrl: item.webUrl,\n downloadUrl,\n },\n };\n}\n\nexport async function uploadFile(\n fileName: string,\n content: string,\n parentFolderId: string = \"root\",\n): Promise<DriveItem> {\n const token = await getTokenOrThrow();\n const endpoint = `${GRAPH_API_URL}/me/drive/items/${parentFolderId}:/${fileName}:/content`;\n\n const response = await fetch(endpoint, {\n method: \"PUT\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/octet-stream\",\n },\n body: content,\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(`Failed to upload file: ${error.error?.message ?? response.statusText}`);\n }\n\n return response.json();\n}\n\nexport function createFolder(\n folderName: string,\n parentFolderId: string = \"root\",\n): Promise<DriveItem> {\n return onedriveFetch<DriveItem>(`/me/drive/items/${parentFolderId}/children`, {\n method: \"POST\",\n body: JSON.stringify({\n name: folderName,\n folder: {},\n \"@microsoft.graph.conflictBehavior\": \"rename\",\n }),\n });\n}\n\nexport function searchFiles(\n query: string,\n options?: {\n top?: number;\n },\n): Promise<SearchResult> {\n const params = new URLSearchParams({ q: query });\n if (options?.top) params.set(\"$top\", options.top.toString());\n\n return onedriveFetch<SearchResult>(\n `/me/drive/root/search(q='${encodeURIComponent(query)}')?${params.toString()}`,\n );\n}\n\nexport async function deleteFile(itemId: string): Promise<void> {\n const token = await getTokenOrThrow();\n\n const response = await fetch(`${GRAPH_API_URL}/me/drive/items/${itemId}`, {\n method: \"DELETE\",\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(`Failed to delete item: ${error.error?.message ?? response.statusText}`);\n }\n}\n\nexport function moveFile(\n itemId: string,\n newParentId: string,\n newName?: string,\n): Promise<DriveItem> {\n const body: Record<string, unknown> = {\n parentReference: { id: newParentId },\n };\n\n if (newName) body.name = newName;\n\n return onedriveFetch<DriveItem>(`/me/drive/items/${itemId}`, {\n method: \"PATCH\",\n body: JSON.stringify(body),\n });\n}\n\n// Helper Functions\n\nexport function formatFileSize(bytes: number): string {\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n let size = bytes;\n let unitIndex = 0;\n\n while (size >= 1024 && unitIndex < units.length - 1) {\n size /= 1024;\n unitIndex++;\n }\n\n return `${size.toFixed(2)} ${units[unitIndex]}`;\n}\n\nexport function isFile(item: DriveItem): boolean {\n return item.file !== undefined;\n}\n\nexport function isFolder(item: DriveItem): boolean {\n return item.folder !== undefined;\n}\n",
|
|
481
|
+
"tools/upload-file.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatFileSize, uploadFile } from \"../../lib/onedrive-client.ts\";\n\nexport default tool({\n id: \"upload-file\",\n description:\n \"Upload or update a file in OneDrive. Can create new files or overwrite existing ones.\",\n inputSchema: z.object({\n fileName: z\n .string()\n .describe(\"Name of the file to upload (e.g., 'notes.txt', 'document.pdf')\"),\n content: z.string().describe(\"The content to write to the file\"),\n parentFolderId: z\n .string()\n .default(\"root\")\n .describe('Parent folder ID where the file should be uploaded (default: \"root\")'),\n }),\n async execute({ fileName, content, parentFolderId }) {\n const trimmedName = fileName.trim();\n\n if (!trimmedName) {\n throw new Error(\"Filename cannot be empty\");\n }\n\n if (trimmedName.includes(\"/\") || trimmedName.includes(\"\\\\\")) {\n throw new Error(\"Filename cannot contain path separators\");\n }\n\n const result = await uploadFile(trimmedName, content, parentFolderId);\n const size = result.size ?? 0;\n\n return {\n success: true,\n id: result.id,\n name: result.name,\n webUrl: result.webUrl,\n size,\n sizeFormatted: formatFileSize(size),\n createdDateTime: result.createdDateTime,\n lastModifiedDateTime: result.lastModifiedDateTime,\n message: `File uploaded successfully: ${result.name}`,\n };\n },\n});\n",
|
|
482
|
+
"tools/download-file.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { downloadFile, formatFileSize } from \"../../lib/onedrive-client.ts\";\n\nexport default tool({\n id: \"download-file\",\n description: \"Download file content from OneDrive. Returns the file content and metadata.\",\n inputSchema: z.object({\n itemId: z.string().describe(\"The ID of the file to download\"),\n preview: z\n .boolean()\n .default(false)\n .describe(\"If true, return only first 1000 characters as preview\"),\n }),\n async execute({ itemId, preview }) {\n const { content, metadata } = await downloadFile(itemId);\n\n const isTruncated = preview && content.length > 1000;\n\n return {\n content: preview ? content.substring(0, 1000) : content,\n isTruncated,\n metadata: {\n id: metadata.id,\n name: metadata.name,\n size: metadata.size,\n sizeFormatted: formatFileSize(metadata.size),\n mimeType: metadata.mimeType,\n createdDateTime: metadata.createdDateTime,\n lastModifiedDateTime: metadata.lastModifiedDateTime,\n webUrl: metadata.webUrl,\n },\n message: isTruncated\n ? `Retrieved preview (first 1000 characters) of ${metadata.name}`\n : `Retrieved full content of ${metadata.name}`,\n };\n },\n});\n",
|
|
483
|
+
"tools/search-files.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatFileSize, isFile, isFolder, searchFiles } from \"../../lib/onedrive-client.ts\";\n\nexport default tool({\n id: \"search-files\",\n description:\n \"Search for files and folders in OneDrive by name or content. Returns matching items with their paths and metadata.\",\n inputSchema: z.object({\n query: z.string().describe(\"Search query to find files or folders\"),\n maxResults: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of results to return\"),\n }),\n async execute({ query, maxResults }) {\n const result = await searchFiles(query, { top: maxResults });\n\n const matches = result.value.map((item) => {\n const baseInfo = {\n id: item.id,\n name: item.name,\n webUrl: item.webUrl,\n createdDateTime: item.createdDateTime,\n lastModifiedDateTime: item.lastModifiedDateTime,\n parentPath: item.parentReference?.path,\n };\n\n if (isFile(item)) {\n const size = item.size ?? 0;\n\n return {\n ...baseInfo,\n type: \"file\" as const,\n size,\n sizeFormatted: formatFileSize(size),\n mimeType: item.file?.mimeType,\n };\n }\n\n if (isFolder(item)) {\n return {\n ...baseInfo,\n type: \"folder\" as const,\n childCount: item.folder?.childCount ?? 0,\n };\n }\n\n return {\n ...baseInfo,\n type: \"unknown\" as const,\n };\n });\n\n return {\n matches,\n count: matches.length,\n hasMore: Boolean(result[\"@odata.nextLink\"]),\n query,\n };\n },\n});\n",
|
|
484
|
+
"tools/list-files.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatFileSize, isFile, isFolder, listFiles } from \"../../lib/onedrive-client.ts\";\n\nexport default tool({\n id: \"list-files\",\n description:\n \"List files and folders in a OneDrive folder. Returns file/folder names, types, sizes, and modification dates.\",\n inputSchema: z.object({\n folderId: z\n .string()\n .default(\"root\")\n .describe('Folder ID or \"root\" for the root folder'),\n orderBy: z\n .string()\n .optional()\n .describe('Order by field (e.g., \"name\", \"lastModifiedDateTime desc\")'),\n limit: z\n .number()\n .min(1)\n .max(200)\n .default(100)\n .describe(\"Maximum number of items to return\"),\n }),\n async execute({ folderId, orderBy, limit }) {\n const result = await listFiles(folderId, { orderBy, top: limit });\n\n const items = result.value.map((item) => {\n const baseInfo = {\n id: item.id,\n name: item.name,\n webUrl: item.webUrl,\n createdDateTime: item.createdDateTime,\n lastModifiedDateTime: item.lastModifiedDateTime,\n };\n\n if (isFile(item)) {\n const size = item.size ?? 0;\n\n return {\n ...baseInfo,\n type: \"file\" as const,\n size,\n sizeFormatted: formatFileSize(size),\n mimeType: item.file?.mimeType,\n };\n }\n\n if (isFolder(item)) {\n return {\n ...baseInfo,\n type: \"folder\" as const,\n childCount: item.folder?.childCount ?? 0,\n };\n }\n\n return {\n ...baseInfo,\n type: \"unknown\" as const,\n };\n });\n\n return {\n items,\n count: items.length,\n hasMore: Boolean(result[\"@odata.nextLink\"]),\n };\n },\n});\n",
|
|
485
|
+
"app/api/auth/onedrive/route.ts": "import { createOAuthInitHandler, memoryTokenStore, oneDriveConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(oneDriveConfig, { tokenStore: memoryTokenStore });\n",
|
|
486
|
+
"app/api/auth/onedrive/callback/route.ts": "/**\n * OneDrive OAuth Callback\n *\n * Handles the OAuth callback from Microsoft and stores the tokens.\n */\n\nimport { createOAuthCallbackHandler, memoryTokenStore, oneDriveConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\n// Hybrid adapter: uses framework's memoryTokenStore for state (PKCE),\n// but user's tokenStore for actual token storage\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(oneDriveConfig, { tokenStore: hybridTokenStore });\n",
|
|
487
|
+
".env.example": "# OneDrive Integration Environment Variables\n\n# Microsoft Azure App Client ID\n# Get this from https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\nMICROSOFT_CLIENT_ID=your_client_id_here\n\n# Microsoft Azure App Client Secret\n# Get this from https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\nMICROSOFT_CLIENT_SECRET=your_client_secret_here\n\n# Setup Instructions:\n# 1. Go to https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\n# 2. Create a new app registration or select an existing one\n# 3. Note the Application (client) ID\n# 4. Create a new client secret under \"Certificates & secrets\"\n# 5. Add the OAuth2 redirect URI under \"Authentication\": http://localhost:3000/api/auth/onedrive/callback\n# 6. Grant the following Microsoft Graph API permissions under \"API permissions\":\n# - Files.Read\n# - Files.ReadWrite\n# - Files.Read.All\n# - Files.ReadWrite.All\n# - offline_access\n# 7. Grant admin consent if required by your organization\n"
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
"integration:zoom": {
|
|
491
|
+
"files": {
|
|
492
|
+
"lib/zoom-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst ZOOM_BASE_URL = \"https://api.zoom.us/v2\";\n\ninterface ZoomMeeting {\n id: number;\n uuid: string;\n topic: string;\n type: number;\n start_time: string;\n duration: number;\n timezone: string;\n agenda: string;\n created_at: string;\n join_url: string;\n password?: string;\n host_id: string;\n host_email: string;\n status: string;\n settings?: {\n host_video: boolean;\n participant_video: boolean;\n join_before_host: boolean;\n mute_upon_entry: boolean;\n watermark: boolean;\n audio: string;\n auto_recording: string;\n };\n}\n\ninterface ZoomUser {\n id: string;\n first_name: string;\n last_name: string;\n email: string;\n type: number;\n pmi: number;\n timezone: string;\n verified: number;\n created_at: string;\n last_login_time: string;\n pic_url: string;\n}\n\ninterface ZoomMeetingList {\n meetings: ZoomMeeting[];\n page_count: number;\n page_number: number;\n page_size: number;\n total_records: number;\n}\n\ninterface MeetingSettingsInput {\n hostVideo?: boolean;\n participantVideo?: boolean;\n joinBeforeHost?: boolean;\n muteUponEntry?: boolean;\n watermark?: boolean;\n audio?: \"both\" | \"telephony\" | \"voip\";\n autoRecording?: \"local\" | \"cloud\" | \"none\";\n}\n\nfunction toZoomSettings(settings?: MeetingSettingsInput): Record<string, unknown> | undefined {\n if (!settings) return undefined;\n\n return {\n host_video: settings.hostVideo,\n participant_video: settings.participantVideo,\n join_before_host: settings.joinBeforeHost,\n mute_upon_entry: settings.muteUponEntry,\n watermark: settings.watermark,\n audio: settings.audio,\n auto_recording: settings.autoRecording,\n };\n}\n\nasync function zoomFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Zoom. Please connect your account.\");\n }\n\n const response = await fetch(`${ZOOM_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as { message?: string }));\n throw new Error(\n `Zoom API error: ${response.status} ${error.message || response.statusText}`,\n );\n }\n\n return response.json();\n}\n\nexport async function getUser(): Promise<ZoomUser> {\n return zoomFetch<ZoomUser>(\"/users/me\");\n}\n\nexport async function listMeetings(\n options: {\n userId?: string;\n type?: \"scheduled\" | \"live\" | \"upcoming\" | \"upcoming_meetings\" | \"previous_meetings\";\n pageSize?: number;\n pageNumber?: number;\n } = {},\n): Promise<ZoomMeeting[]> {\n const userId = options.userId ?? \"me\";\n const params = new URLSearchParams({\n type: options.type ?? \"scheduled\",\n page_size: String(options.pageSize ?? 30),\n page_number: String(options.pageNumber ?? 1),\n });\n\n const response = await zoomFetch<ZoomMeetingList>(`/users/${userId}/meetings?${params}`);\n return response.meetings;\n}\n\nexport async function getMeeting(meetingId: string | number): Promise<ZoomMeeting> {\n return zoomFetch<ZoomMeeting>(`/meetings/${meetingId}`);\n}\n\nexport async function createMeeting(options: {\n userId?: string;\n topic: string;\n type?: 1 | 2 | 3 | 8; // 1=Instant, 2=Scheduled, 3=Recurring with no fixed time, 8=Recurring with fixed time\n startTime?: string; // ISO 8601 format\n duration?: number; // In minutes\n timezone?: string;\n password?: string;\n agenda?: string;\n settings?: MeetingSettingsInput;\n}): Promise<ZoomMeeting> {\n const userId = options.userId ?? \"me\";\n const body: Record<string, unknown> = {\n topic: options.topic,\n type: options.type ?? 2,\n };\n\n if (options.startTime) body.start_time = options.startTime;\n if (options.duration) body.duration = options.duration;\n if (options.timezone) body.timezone = options.timezone;\n if (options.password) body.password = options.password;\n if (options.agenda) body.agenda = options.agenda;\n\n const settings = toZoomSettings(options.settings);\n if (settings) body.settings = settings;\n\n return zoomFetch<ZoomMeeting>(`/users/${userId}/meetings`, {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport async function updateMeeting(\n meetingId: string | number,\n updates: {\n topic?: string;\n type?: 1 | 2 | 3 | 8;\n startTime?: string;\n duration?: number;\n timezone?: string;\n password?: string;\n agenda?: string;\n settings?: MeetingSettingsInput;\n },\n): Promise<void> {\n const body: Record<string, unknown> = {};\n\n if (updates.topic !== undefined) body.topic = updates.topic;\n if (updates.type !== undefined) body.type = updates.type;\n if (updates.startTime !== undefined) body.start_time = updates.startTime;\n if (updates.duration !== undefined) body.duration = updates.duration;\n if (updates.timezone !== undefined) body.timezone = updates.timezone;\n if (updates.password !== undefined) body.password = updates.password;\n if (updates.agenda !== undefined) body.agenda = updates.agenda;\n\n const settings = toZoomSettings(updates.settings);\n if (settings) body.settings = settings;\n\n await zoomFetch<void>(`/meetings/${meetingId}`, {\n method: \"PATCH\",\n body: JSON.stringify(body),\n });\n}\n\nexport async function deleteMeeting(\n meetingId: string | number,\n options?: {\n occurrenceId?: string;\n scheduleForReminder?: boolean;\n },\n): Promise<void> {\n const params = new URLSearchParams();\n if (options?.occurrenceId) params.set(\"occurrence_id\", options.occurrenceId);\n if (options?.scheduleForReminder !== undefined) {\n params.set(\"schedule_for_reminder\", String(options.scheduleForReminder));\n }\n\n const queryString = params.toString();\n await zoomFetch<void>(`/meetings/${meetingId}${queryString ? `?${queryString}` : \"\"}`, {\n method: \"DELETE\",\n });\n}\n",
|
|
493
|
+
"tools/delete-meeting.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { deleteMeeting } from \"../../lib/zoom-client.ts\";\n\nexport default tool({\n id: \"delete-meeting\",\n description: \"Delete a scheduled Zoom meeting.\",\n inputSchema: z.object({\n meetingId: z.union([z.string(), z.number()]).describe(\"The meeting ID to delete\"),\n occurrenceId: z\n .string()\n .optional()\n .describe(\"The meeting occurrence ID for recurring meetings\"),\n scheduleForReminder: z\n .boolean()\n .default(false)\n .describe(\"Whether to send a reminder email to participants\"),\n }),\n async execute({ meetingId, occurrenceId, scheduleForReminder }) {\n await deleteMeeting(meetingId, { occurrenceId, scheduleForReminder });\n\n return {\n success: true,\n message: `Meeting ${meetingId} deleted successfully`,\n };\n },\n});\n",
|
|
494
|
+
"tools/create-meeting.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createMeeting } from \"../../lib/zoom-client.ts\";\n\nexport default tool({\n id: \"create-meeting\",\n description: \"Create a new Zoom meeting with specified settings.\",\n inputSchema: z.object({\n topic: z.string().describe(\"The topic/title of the meeting\"),\n type: z\n .enum([\"1\", \"2\", \"3\", \"8\"])\n .transform((val) => parseInt(val, 10) as 1 | 2 | 3 | 8)\n .default(\"2\")\n .describe(\n \"Meeting type: 1=Instant, 2=Scheduled, 3=Recurring with no fixed time, 8=Recurring with fixed time\",\n ),\n startTime: z\n .string()\n .optional()\n .describe(\"Start time in ISO 8601 format (e.g., 2024-12-07T10:00:00Z)\"),\n duration: z.number().min(1).optional().describe(\"Meeting duration in minutes\"),\n timezone: z\n .string()\n .optional()\n .describe(\"Timezone (e.g., America/New_York, Europe/London)\"),\n password: z.string().optional().describe(\"Meeting password\"),\n agenda: z.string().optional().describe(\"Meeting agenda or description\"),\n hostVideo: z.boolean().default(true).describe(\"Start video when host joins\"),\n participantVideo: z\n .boolean()\n .default(true)\n .describe(\"Start video when participants join\"),\n joinBeforeHost: z\n .boolean()\n .default(false)\n .describe(\"Allow participants to join before host\"),\n muteUponEntry: z\n .boolean()\n .default(false)\n .describe(\"Mute participants upon entry\"),\n autoRecording: z\n .enum([\"local\", \"cloud\", \"none\"])\n .default(\"none\")\n .describe(\"Automatic recording setting\"),\n }),\n async execute(input) {\n const meeting = await createMeeting({\n topic: input.topic,\n type: input.type,\n startTime: input.startTime,\n duration: input.duration,\n timezone: input.timezone,\n password: input.password,\n agenda: input.agenda,\n settings: {\n hostVideo: input.hostVideo,\n participantVideo: input.participantVideo,\n joinBeforeHost: input.joinBeforeHost,\n muteUponEntry: input.muteUponEntry,\n autoRecording: input.autoRecording,\n audio: \"both\",\n },\n });\n\n return {\n success: true,\n meeting: {\n id: meeting.id,\n uuid: meeting.uuid,\n topic: meeting.topic,\n startTime: meeting.start_time,\n duration: meeting.duration,\n timezone: meeting.timezone,\n joinUrl: meeting.join_url,\n password: meeting.password,\n hostEmail: meeting.host_email,\n },\n };\n },\n});\n",
|
|
495
|
+
"tools/update-meeting.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { updateMeeting } from \"../../lib/zoom-client.ts\";\n\nexport default tool({\n id: \"update-meeting\",\n description: \"Update an existing Zoom meeting with new settings.\",\n inputSchema: z.object({\n meetingId: z.union([z.string(), z.number()]).describe(\"The meeting ID to update\"),\n topic: z.string().optional().describe(\"The new topic/title of the meeting\"),\n type: z\n .enum([\"1\", \"2\", \"3\", \"8\"])\n .transform((val) => parseInt(val, 10) as 1 | 2 | 3 | 8)\n .optional()\n .describe(\n \"Meeting type: 1=Instant, 2=Scheduled, 3=Recurring with no fixed time, 8=Recurring with fixed time\",\n ),\n startTime: z.string().optional().describe(\"New start time in ISO 8601 format\"),\n duration: z.number().min(1).optional().describe(\"New meeting duration in minutes\"),\n timezone: z.string().optional().describe(\"New timezone\"),\n password: z.string().optional().describe(\"New meeting password\"),\n agenda: z.string().optional().describe(\"New meeting agenda or description\"),\n hostVideo: z.boolean().optional().describe(\"Start video when host joins\"),\n participantVideo: z.boolean().optional().describe(\"Start video when participants join\"),\n joinBeforeHost: z.boolean().optional().describe(\"Allow participants to join before host\"),\n muteUponEntry: z.boolean().optional().describe(\"Mute participants upon entry\"),\n autoRecording: z.enum([\"local\", \"cloud\", \"none\"]).optional().describe(\"Automatic recording setting\"),\n }),\n async execute({\n meetingId,\n topic,\n type,\n startTime,\n duration,\n timezone,\n password,\n agenda,\n hostVideo,\n participantVideo,\n joinBeforeHost,\n muteUponEntry,\n autoRecording,\n }): Promise<{ success: true; message: string }> {\n const hasSettings =\n hostVideo !== undefined ||\n participantVideo !== undefined ||\n joinBeforeHost !== undefined ||\n muteUponEntry !== undefined ||\n autoRecording !== undefined;\n\n await updateMeeting(meetingId, {\n topic,\n type,\n startTime,\n duration,\n timezone,\n password,\n agenda,\n settings: hasSettings\n ? { hostVideo, participantVideo, joinBeforeHost, muteUponEntry, autoRecording }\n : undefined,\n });\n\n return {\n success: true,\n message: `Meeting ${meetingId} updated successfully`,\n };\n },\n});\n",
|
|
496
|
+
"tools/get-meeting.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getMeeting } from \"../../lib/zoom-client.ts\";\n\nexport default tool({\n id: \"get-meeting\",\n description: \"Get detailed information about a specific Zoom meeting by its ID.\",\n inputSchema: z.object({\n meetingId: z.union([z.string(), z.number()]).describe(\"The meeting ID or UUID\"),\n }),\n async execute({ meetingId }) {\n const meeting = await getMeeting(meetingId);\n const settings = meeting.settings;\n\n return {\n id: meeting.id,\n uuid: meeting.uuid,\n topic: meeting.topic,\n type: meeting.type,\n startTime: meeting.start_time,\n duration: meeting.duration,\n timezone: meeting.timezone,\n agenda: meeting.agenda,\n joinUrl: meeting.join_url,\n password: meeting.password,\n hostId: meeting.host_id,\n hostEmail: meeting.host_email,\n status: meeting.status,\n createdAt: meeting.created_at,\n settings: settings\n ? {\n hostVideo: settings.host_video,\n participantVideo: settings.participant_video,\n joinBeforeHost: settings.join_before_host,\n muteUponEntry: settings.mute_upon_entry,\n watermark: settings.watermark,\n audio: settings.audio,\n autoRecording: settings.auto_recording,\n }\n : undefined,\n };\n },\n});\n",
|
|
497
|
+
"tools/list-meetings.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listMeetings } from \"../../lib/zoom-client.ts\";\n\nexport default tool({\n id: \"list-meetings\",\n description:\n \"List Zoom meetings for the current user. Can filter by meeting type (scheduled, live, upcoming, etc.).\",\n inputSchema: z.object({\n type: z\n .enum([\"scheduled\", \"live\", \"upcoming\", \"upcoming_meetings\", \"previous_meetings\"])\n .default(\"scheduled\")\n .describe(\"Type of meetings to list\"),\n pageSize: z\n .number()\n .min(1)\n .max(300)\n .default(30)\n .describe(\"Number of meetings to return per page\"),\n pageNumber: z.number().min(1).default(1).describe(\"Page number for pagination\"),\n }),\n async execute({ type, pageSize, pageNumber }) {\n const meetings = await listMeetings({ type, pageSize, pageNumber });\n\n return meetings.map(\n ({\n id,\n uuid,\n topic,\n type: meetingType,\n start_time,\n duration,\n timezone,\n agenda,\n join_url,\n password,\n status,\n }) => ({\n id,\n uuid,\n topic,\n type: meetingType,\n startTime: start_time,\n duration,\n timezone,\n agenda,\n joinUrl: join_url,\n password,\n status,\n }),\n );\n },\n});\n",
|
|
498
|
+
"app/api/auth/zoom/route.ts": "import { createOAuthInitHandler, zoomConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(zoomConfig);\n",
|
|
499
|
+
"app/api/auth/zoom/callback/route.ts": "import { createOAuthCallbackHandler, memoryTokenStore, zoomConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(zoomConfig, { tokenStore: hybridTokenStore });\n",
|
|
500
|
+
".env.example": "# Zoom OAuth Configuration\n# Get your credentials from https://marketplace.zoom.us/develop/create\nZOOM_CLIENT_ID=your-client-id\nZOOM_CLIENT_SECRET=your-client-secret\n"
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
"integration:neon": {
|
|
504
|
+
"files": {
|
|
505
|
+
"lib/neon-client.ts": "import { getApiKey, getDatabaseUrl } from \"./token-store.ts\";\nimport { Client } from \"pg\";\n\nconst NEON_API_BASE_URL = \"https://console.neon.tech/api/v2\";\n\ninterface NeonProject {\n id: string;\n platform_id: string;\n region_id: string;\n name: string;\n provisioner: string;\n default_endpoint_settings?: {\n autoscaling_limit_min_cu: number;\n autoscaling_limit_max_cu: number;\n suspend_timeout_seconds: number;\n };\n settings?: {\n quota?: {\n active_time_seconds?: number;\n compute_time_seconds?: number;\n written_data_bytes?: number;\n data_transfer_bytes?: number;\n };\n };\n pg_version: number;\n store_passwords: boolean;\n creation_source: string;\n created_at: string;\n updated_at: string;\n proxy_host: string;\n branch_logical_size_limit: number;\n branch_logical_size_limit_bytes: number;\n cpu_used_sec: number;\n maintenance_starts_at?: string;\n}\n\ninterface NeonBranch {\n id: string;\n project_id: string;\n parent_id?: string;\n parent_lsn?: string;\n parent_timestamp?: string;\n name: string;\n current_state: string;\n pending_state?: string;\n logical_size?: number;\n creation_source: string;\n primary?: boolean;\n default?: boolean;\n protected?: boolean;\n cpu_used_sec: number;\n compute_time_sec?: number;\n active_time_sec?: number;\n written_data_bytes?: number;\n data_transfer_bytes?: number;\n created_at: string;\n updated_at: string;\n}\n\ninterface NeonProjectsResponse {\n projects: NeonProject[];\n}\n\ninterface NeonBranchesResponse {\n branches: NeonBranch[];\n}\n\ninterface NeonEndpoint {\n host: string;\n id: string;\n project_id: string;\n branch_id: string;\n autoscaling_limit_min_cu: number;\n autoscaling_limit_max_cu: number;\n region_id: string;\n type: string;\n current_state: string;\n settings: {\n pg_settings?: Record<string, string>;\n };\n pooler_enabled: boolean;\n pooler_mode?: string;\n disabled: boolean;\n passwordless_access: boolean;\n creation_source: string;\n created_at: string;\n updated_at: string;\n proxy_host: string;\n suspend_timeout_seconds: number;\n provisioner: string;\n}\n\ninterface NeonEndpointsResponse {\n endpoints: NeonEndpoint[];\n}\n\ninterface TableInfo {\n tablename: string;\n schemaname: string;\n tableowner: string;\n}\n\ninterface ColumnInfo {\n column_name: string;\n data_type: string;\n is_nullable: string;\n column_default: string | null;\n character_maximum_length: number | null;\n}\n\nasync function neonFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const apiKey = getApiKey() ?? process.env.NEON_API_KEY;\n if (!apiKey) {\n throw new Error(\"Not authenticated with Neon. Please set NEON_API_KEY.\");\n }\n\n const response = await fetch(`${NEON_API_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as { message?: string }));\n throw new Error(\n `Neon API error: ${response.status} ${error.message || response.statusText}`,\n );\n }\n\n return response.json() as Promise<T>;\n}\n\nexport async function listProjects(): Promise<NeonProject[]> {\n const { projects } = await neonFetch<NeonProjectsResponse>(\"/projects\");\n return projects;\n}\n\nexport function getProject(projectId: string): Promise<NeonProject> {\n return neonFetch<NeonProject>(`/projects/${projectId}`);\n}\n\nexport async function listBranches(projectId: string): Promise<NeonBranch[]> {\n const { branches } = await neonFetch<NeonBranchesResponse>(\n `/projects/${projectId}/branches`,\n );\n return branches;\n}\n\nexport async function createBranch(\n projectId: string,\n options: {\n name?: string;\n parentId?: string;\n parentLsn?: string;\n parentTimestamp?: string;\n },\n): Promise<NeonBranch> {\n const branch: Record<string, unknown> = { name: options.name };\n\n if (options.parentId) branch.parent_id = options.parentId;\n if (options.parentLsn) branch.parent_lsn = options.parentLsn;\n if (options.parentTimestamp) branch.parent_timestamp = options.parentTimestamp;\n\n const { branch: createdBranch } = await neonFetch<{ branch: NeonBranch }>(\n `/projects/${projectId}/branches`,\n {\n method: \"POST\",\n body: JSON.stringify({ branch }),\n },\n );\n\n return createdBranch;\n}\n\nexport async function listEndpoints(projectId: string): Promise<NeonEndpoint[]> {\n const { endpoints } = await neonFetch<NeonEndpointsResponse>(\n `/projects/${projectId}/endpoints`,\n );\n return endpoints;\n}\n\nasync function getDbClient(): Promise<Client> {\n const databaseUrl = getDatabaseUrl();\n if (!databaseUrl) {\n throw new Error(\n \"DATABASE_URL not configured. Please set DATABASE_URL environment variable.\",\n );\n }\n\n const client = new Client({\n connectionString: databaseUrl,\n ssl: { rejectUnauthorized: false },\n });\n\n await client.connect();\n return client;\n}\n\nexport async function query<T = Record<string, unknown>>(\n sql: string,\n params?: unknown[],\n): Promise<{ rows: T[]; rowCount: number }> {\n const client = await getDbClient();\n\n try {\n const result = await client.query(sql, params);\n return {\n rows: result.rows as T[],\n rowCount: result.rowCount ?? 0,\n };\n } finally {\n await client.end();\n }\n}\n\nexport async function listTables(schema: string = \"public\"): Promise<TableInfo[]> {\n const result = await query<TableInfo>(\n `SELECT tablename, schemaname, tableowner\n FROM pg_tables\n WHERE schemaname = $1\n ORDER BY tablename`,\n [schema],\n );\n\n return result.rows;\n}\n\nexport async function describeTable(\n tableName: string,\n schema: string = \"public\",\n): Promise<{ tableName: string; schema: string; columns: ColumnInfo[] }> {\n const result = await query<ColumnInfo>(\n `SELECT\n column_name,\n data_type,\n is_nullable,\n column_default,\n character_maximum_length\n FROM information_schema.columns\n WHERE table_schema = $1 AND table_name = $2\n ORDER BY ordinal_position`,\n [schema, tableName],\n );\n\n return { tableName, schema, columns: result.rows };\n}\n\nexport async function getTableRowCount(\n tableName: string,\n schema: string = \"public\",\n): Promise<number> {\n const result = await query<{ count: string }>(\n `SELECT COUNT(*) as count FROM \"${schema}\".\"${tableName}\"`,\n );\n\n return parseInt(result.rows[0]?.count ?? \"0\", 10);\n}\n",
|
|
506
|
+
"tools/query-database.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { query } from \"../../lib/neon-client.ts\";\n\nexport default tool({\n id: \"query-database\",\n description:\n \"Execute SQL queries against the connected Neon database. Supports parameterized queries for safety. Use this to retrieve, analyze, or search data.\",\n inputSchema: z.object({\n sql: z.string().describe(\"SQL query to execute. Use $1, $2, etc. for parameters\"),\n params: z\n .array(z.union([z.string(), z.number(), z.boolean(), z.null()]))\n .optional()\n .describe(\"Optional array of parameter values for the query\"),\n limit: z.number().min(1).max(1000).default(100).describe(\"Maximum number of rows to return\"),\n }),\n async execute({ sql, params, limit }) {\n const trimmedSql = sql.trim();\n const isSelectQuery = /^SELECT/i.test(trimmedSql);\n\n const finalSql =\n isSelectQuery && !/LIMIT\\s+\\d+/i.test(trimmedSql) ? `${trimmedSql} LIMIT ${limit}` : trimmedSql;\n\n const result = await query(finalSql, params);\n\n return {\n rows: result.rows,\n rowCount: result.rowCount,\n limited: isSelectQuery && result.rowCount >= limit,\n };\n },\n});\n",
|
|
507
|
+
"tools/list-projects.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listProjects } from \"../../lib/neon-client.ts\";\n\nexport default tool({\n id: \"list-projects\",\n description:\n \"List all Neon projects in your account. Returns project details including name, region, PostgreSQL version, and creation date.\",\n inputSchema: z.object({}),\n async execute() {\n const projects = await listProjects();\n\n return projects.map((project) => {\n const settings = project.default_endpoint_settings;\n\n return {\n id: project.id,\n name: project.name,\n region: project.region_id,\n pgVersion: project.pg_version,\n proxyHost: project.proxy_host,\n createdAt: project.created_at,\n updatedAt: project.updated_at,\n cpuUsedSec: project.cpu_used_sec,\n autoscaling: settings\n ? {\n minCu: settings.autoscaling_limit_min_cu,\n maxCu: settings.autoscaling_limit_max_cu,\n suspendTimeout: settings.suspend_timeout_seconds,\n }\n : undefined,\n };\n });\n },\n});\n",
|
|
508
|
+
"tools/describe-table.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { describeTable, getTableRowCount } from \"../../lib/neon-client.ts\";\n\nexport default tool({\n id: \"describe-table\",\n description:\n \"Get detailed schema information for a specific table including column names, data types, nullability, defaults, and constraints.\",\n inputSchema: z.object({\n tableName: z.string().describe(\"Name of the table to describe\"),\n schema: z.string().default(\"public\").describe(\"Schema name where the table is located\"),\n }),\n async execute({ tableName, schema }) {\n const tableInfo = await describeTable(tableName, schema);\n\n const rowCount = await getTableRowCount(tableName, schema).catch(() => undefined);\n\n return {\n tableName: tableInfo.tableName,\n schema: tableInfo.schema,\n rowCount,\n columnCount: tableInfo.columns.length,\n columns: tableInfo.columns.map((col) => ({\n name: col.column_name,\n type: col.data_type,\n nullable: col.is_nullable === \"YES\",\n default: col.column_default,\n maxLength: col.character_maximum_length,\n })),\n };\n },\n});\n",
|
|
509
|
+
"tools/list-branches.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listBranches } from \"../../lib/neon-client.ts\";\n\nexport default tool({\n id: \"list-branches\",\n description:\n \"List all branches for a specific Neon project. Branches are isolated database environments that can be created from any point in time.\",\n inputSchema: z.object({\n projectId: z.string().describe(\"The ID of the Neon project\"),\n }),\n async execute({ projectId }) {\n const branches = await listBranches(projectId);\n\n return branches.map(\n ({\n id,\n project_id,\n name,\n current_state,\n pending_state,\n primary,\n default: isDefault,\n protected: isProtected,\n parent_id,\n parent_lsn,\n parent_timestamp,\n logical_size,\n created_at,\n updated_at,\n cpu_used_sec,\n compute_time_sec,\n active_time_sec,\n }) => ({\n id,\n projectId: project_id,\n name,\n currentState: current_state,\n pendingState: pending_state,\n primary,\n default: isDefault,\n protected: isProtected,\n parentId: parent_id,\n parentLsn: parent_lsn,\n parentTimestamp: parent_timestamp,\n logicalSize: logical_size,\n createdAt: created_at,\n updatedAt: updated_at,\n cpuUsedSec: cpu_used_sec,\n computeTimeSec: compute_time_sec,\n activeTimeSec: active_time_sec,\n }),\n );\n },\n});\n",
|
|
510
|
+
"tools/list-tables.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getTableRowCount, listTables } from \"../../lib/neon-client.ts\";\n\nexport default tool({\n id: \"list-tables\",\n description:\n \"List all tables in the connected database. Returns table names, schemas, and row counts to help understand the database structure.\",\n inputSchema: z.object({\n schema: z.string().default(\"public\").describe(\"Schema name to list tables from\"),\n includeRowCounts: z\n .boolean()\n .default(false)\n .describe(\"Whether to include row counts for each table (slower but more informative)\"),\n }),\n async execute({ schema, includeRowCounts }) {\n const tables = await listTables(schema);\n\n const results = [];\n for (const table of tables) {\n const result: {\n tablename: string;\n schemaname: string;\n tableowner: string;\n rowCount?: number;\n } = {\n tablename: table.tablename,\n schemaname: table.schemaname,\n tableowner: table.tableowner,\n };\n\n if (includeRowCounts) {\n try {\n result.rowCount = await getTableRowCount(table.tablename, schema);\n } catch {\n result.rowCount = undefined;\n }\n }\n\n results.push(result);\n }\n\n return {\n schema,\n tableCount: results.length,\n tables: results,\n };\n },\n});\n",
|
|
511
|
+
"app/api/auth/neon/route.ts": "import { setApiKey } from \"../../../../lib/token-store.ts\";\n\nexport async function POST(request: Request): Promise<Response> {\n try {\n const { apiKey, databaseUrl } = await request.json();\n\n if (!apiKey) {\n return Response.json({ error: \"API key is required\" }, { status: 400 });\n }\n\n const response = await fetch(\"https://console.neon.tech/api/v2/projects\", {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n return Response.json({ error: \"Invalid API key\" }, { status: 401 });\n }\n\n setApiKey(apiKey, databaseUrl);\n\n return Response.json({\n success: true,\n message: \"Successfully authenticated with Neon\",\n });\n } catch (error) {\n console.error(\"Neon auth error:\", error);\n return Response.json({ error: \"Authentication failed\" }, { status: 500 });\n }\n}\n\nexport function GET(): Response {\n return Response.json({\n authenticated: false,\n message: \"Use POST to authenticate with API key\",\n });\n}\n",
|
|
512
|
+
".env.example": "# Neon Integration\n# Create an API key at https://console.neon.tech/app/settings/api-keys\n# Get your connection string from your Neon project dashboard\n\nNEON_API_KEY=your_api_key_here\nDATABASE_URL=postgres://user:password@ep-xxxx.region.neon.tech/dbname?sslmode=require\n"
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
"integration:posthog": {
|
|
516
|
+
"files": {
|
|
517
|
+
"lib/posthog-client.ts": "import { getApiKey } from \"./token-store.ts\";\n\nconst DEFAULT_POSTHOG_HOST = \"https://app.posthog.com\";\n\nexport interface PostHogInsight {\n id: number;\n name: string;\n derived_name: string | null;\n description: string;\n filters: Record<string, unknown>;\n result: unknown;\n created_at: string;\n created_by: {\n id: number;\n uuid: string;\n distinct_id: string;\n first_name: string;\n email: string;\n } | null;\n}\n\nexport interface PostHogTrend {\n action: {\n id: string;\n name: string;\n type: string;\n };\n label: string;\n count: number;\n data: number[];\n labels: string[];\n days: string[];\n}\n\nexport interface PostHogFunnel {\n id: number;\n name: string;\n steps: Array<{\n action_id: string;\n name: string;\n order: number;\n count: number;\n average_conversion_time: number | null;\n }>;\n filters: Record<string, unknown>;\n}\n\nexport interface PostHogFeatureFlag {\n id: number;\n name: string;\n key: string;\n filters: {\n groups: Array<{\n properties: unknown[];\n rollout_percentage: number | null;\n }>;\n };\n deleted: boolean;\n active: boolean;\n created_at: string;\n created_by: {\n id: number;\n uuid: string;\n distinct_id: string;\n first_name: string;\n email: string;\n } | null;\n is_simple_flag: boolean;\n rollout_percentage: number | null;\n ensure_experience_continuity: boolean;\n}\n\nexport interface PostHogPerson {\n id: string;\n name: string;\n distinct_ids: string[];\n properties: Record<string, unknown>;\n created_at: string;\n uuid: string;\n}\n\nexport interface PostHogEvent {\n event: string;\n distinct_id: string;\n properties?: Record<string, unknown>;\n timestamp?: string;\n}\n\ninterface PostHogListResponse<T> {\n next: string | null;\n previous: string | null;\n results: T[];\n}\n\ninterface PostHogError {\n type: string;\n code: string;\n detail: string;\n attr: string | null;\n}\n\nfunction getPostHogHost(): string {\n return process.env.POSTHOG_HOST ?? DEFAULT_POSTHOG_HOST;\n}\n\nasync function posthogFetch<T>(\n endpoint: string,\n options: RequestInit & { params?: Record<string, string | number | boolean> } = {},\n): Promise<T> {\n const apiKey = getApiKey();\n if (!apiKey) {\n throw new Error(\"Not authenticated with PostHog. Please set POSTHOG_API_KEY.\");\n }\n\n const url = new URL(`${getPostHogHost()}/api${endpoint}`);\n if (options.params) {\n for (const [key, value] of Object.entries(options.params)) {\n url.searchParams.append(key, String(value));\n }\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n ...(options.headers as Record<string, string> | undefined),\n };\n\n const response = await fetch(url.toString(), { ...options, headers });\n const data: unknown = await response.json();\n\n if (!response.ok) {\n const error = data as PostHogError;\n throw new Error(`PostHog API error: ${response.status} ${error.detail || response.statusText}`);\n }\n\n return data as T;\n}\n\nexport function getInsights(options?: {\n limit?: number;\n}): Promise<PostHogListResponse<PostHogInsight>> {\n const params: Record<string, string | number> = {};\n if (options?.limit) params.limit = options.limit;\n\n return posthogFetch<PostHogListResponse<PostHogInsight>>(\"/projects/@current/insights/\", {\n params,\n });\n}\n\nexport function getTrends(options: {\n events?: Array<{ id: string; name?: string; type?: string }>;\n date_from?: string;\n date_to?: string;\n interval?: \"hour\" | \"day\" | \"week\" | \"month\";\n properties?: Record<string, unknown>[];\n}): Promise<PostHogTrend[]> {\n const body = {\n events: options.events ?? [{ id: \"$pageview\", name: \"$pageview\", type: \"events\" }],\n date_from: options.date_from ?? \"-7d\",\n date_to: options.date_to ?? \"now\",\n interval: options.interval ?? \"day\",\n properties: options.properties ?? [],\n };\n\n return posthogFetch<PostHogTrend[]>(\"/projects/@current/insights/trend/\", {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport function getFunnels(options: {\n events?: Array<{ id: string; name?: string; order: number }>;\n date_from?: string;\n date_to?: string;\n}): Promise<PostHogFunnel> {\n const body = {\n events: options.events ?? [],\n date_from: options.date_from ?? \"-7d\",\n date_to: options.date_to ?? \"now\",\n };\n\n return posthogFetch<PostHogFunnel>(\"/projects/@current/insights/funnel/\", {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport function getFeatureFlags(options?: {\n limit?: number;\n}): Promise<PostHogListResponse<PostHogFeatureFlag>> {\n const params: Record<string, string | number> = {};\n if (options?.limit) params.limit = options.limit;\n\n return posthogFetch<PostHogListResponse<PostHogFeatureFlag>>(\n \"/projects/@current/feature_flags/\",\n { params },\n );\n}\n\nexport function getFeatureFlag(flagId: number): Promise<PostHogFeatureFlag> {\n return posthogFetch<PostHogFeatureFlag>(`/projects/@current/feature_flags/${flagId}/`);\n}\n\nexport function listPersons(options?: {\n limit?: number;\n search?: string;\n}): Promise<PostHogListResponse<PostHogPerson>> {\n const params: Record<string, string | number> = {};\n if (options?.limit) params.limit = options.limit;\n if (options?.search) params.search = options.search;\n\n return posthogFetch<PostHogListResponse<PostHogPerson>>(\"/projects/@current/persons/\", {\n params,\n });\n}\n\nexport function getPerson(personId: string): Promise<PostHogPerson> {\n return posthogFetch<PostHogPerson>(`/projects/@current/persons/${personId}/`);\n}\n\nexport function captureEvent(event: PostHogEvent): Promise<{ status: number }> {\n const body = {\n api_key: getApiKey(),\n event: event.event,\n distinct_id: event.distinct_id,\n properties: event.properties ?? {},\n timestamp: event.timestamp ?? new Date().toISOString(),\n };\n\n return posthogFetch<{ status: number }>(\"/capture/\", {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\nexport function formatDate(dateString: string): string {\n return new Date(dateString).toISOString();\n}\n\nexport function calculateConversionRate(funnel: PostHogFunnel): number {\n if (funnel.steps.length < 2) return 0;\n\n const firstStep = funnel.steps[0];\n const lastStep = funnel.steps[funnel.steps.length - 1];\n\n if (firstStep.count === 0) return 0;\n\n return (lastStep.count / firstStep.count) * 100;\n}\n",
|
|
518
|
+
"tools/list-persons.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatDate, listPersons } from \"../../lib/posthog-client.ts\";\n\nexport default tool({\n id: \"list-persons\",\n description:\n \"List persons/users tracked in PostHog. View user properties, distinct IDs, and activity.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of persons to retrieve\"),\n search: z\n .string()\n .optional()\n .describe(\"Search query to filter persons by properties or distinct ID\"),\n }),\n async execute({ limit, search }) {\n const { results } = await listPersons({ limit, search });\n\n return {\n count: results.length,\n persons: results.map((person) => ({\n id: person.id,\n uuid: person.uuid,\n name: person.name,\n distinctIds: person.distinct_ids,\n properties: person.properties,\n createdAt: formatDate(person.created_at),\n })),\n };\n },\n});\n",
|
|
519
|
+
"tools/get-trends.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getTrends } from \"../../lib/posthog-client.ts\";\n\nexport default tool({\n id: \"get-trends\",\n description:\n \"Retrieve event trends and analytics data from PostHog. Analyze how events are trending over time.\",\n inputSchema: z.object({\n events: z\n .array(\n z.object({\n id: z\n .string()\n .describe(\"Event ID or name (e.g., '$pageview', 'button_clicked')\"),\n name: z.string().optional().describe(\"Display name for the event\"),\n type: z.string().optional().default(\"events\").describe(\"Event type\"),\n }),\n )\n .optional()\n .describe(\"List of events to analyze (defaults to $pageview)\"),\n dateFrom: z\n .string()\n .optional()\n .default(\"-7d\")\n .describe(\"Start date in ISO format or relative (e.g., '-7d', '-30d')\"),\n dateTo: z\n .string()\n .optional()\n .default(\"now\")\n .describe(\"End date in ISO format or relative (e.g., 'now', '-1d')\"),\n interval: z\n .enum([\"hour\", \"day\", \"week\", \"month\"])\n .optional()\n .default(\"day\")\n .describe(\"Time interval for aggregation\"),\n }),\n async execute({ events, dateFrom, dateTo, interval }) {\n const trends = await getTrends({\n events,\n date_from: dateFrom,\n date_to: dateTo,\n interval,\n });\n\n return trends.map(({ label, count, data, labels, days, action }) => ({\n label,\n count,\n data,\n labels,\n days,\n action,\n }));\n },\n});\n",
|
|
520
|
+
"tools/capture-event.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { captureEvent } from \"../../lib/posthog-client.ts\";\n\nexport default tool({\n id: \"capture-event\",\n description:\n \"Track a custom event in PostHog. Capture user actions, page views, or any custom analytics event.\",\n inputSchema: z.object({\n event: z.string().describe(\"Event name (e.g., 'button_clicked', 'page_viewed')\"),\n distinctId: z.string().describe(\"Unique identifier for the user or session\"),\n properties: z\n .record(z.unknown())\n .optional()\n .describe(\"Additional properties to attach to the event\"),\n timestamp: z\n .string()\n .optional()\n .describe(\"Event timestamp in ISO format (defaults to current time)\"),\n }),\n async execute({ event, distinctId, properties, timestamp }) {\n const result = await captureEvent({\n event,\n distinct_id: distinctId,\n properties,\n timestamp,\n });\n\n return {\n success: result.status === 1 || result.status === 200,\n event: {\n name: event,\n distinctId,\n properties,\n timestamp: timestamp ?? new Date().toISOString(),\n },\n };\n },\n});\n",
|
|
521
|
+
"tools/list-feature-flags.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatDate, getFeatureFlags } from \"../../lib/posthog-client.ts\";\n\nexport default tool({\n id: \"list-feature-flags\",\n description:\n \"List all feature flags in your PostHog project. View flag status, rollout percentages, and configuration.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of feature flags to retrieve\"),\n }),\n async execute({ limit }) {\n const { results } = await getFeatureFlags({ limit });\n\n return {\n count: results.length,\n flags: results.map((flag) => ({\n id: flag.id,\n name: flag.name,\n key: flag.key,\n active: flag.active,\n deleted: flag.deleted,\n isSimpleFlag: flag.is_simple_flag,\n rolloutPercentage: flag.rollout_percentage,\n createdAt: formatDate(flag.created_at),\n createdBy: flag.created_by\n ? {\n name: flag.created_by.first_name,\n email: flag.created_by.email,\n }\n : null,\n filters: flag.filters,\n })),\n };\n },\n});\n",
|
|
522
|
+
".env.example": "# PostHog Integration\n# Get your API key at https://app.posthog.com/project/settings\n# Create a Personal API Key for server-side access\n\nPOSTHOG_API_KEY=phx_your_api_key_here\nPOSTHOG_HOST=https://app.posthog.com\n"
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
"integration:gmail": {
|
|
526
|
+
"files": {
|
|
527
|
+
"lib/gmail-client.ts": "/**\n * Gmail API Client\n *\n * Provides a type-safe interface to Gmail API operations\n * using the veryfront/oauth module for authentication.\n */\n\nimport { gmailConfig, OAuthService } from \"veryfront/oauth\";\nimport { tokenStore } from \"./token-store.ts\";\n\nexport interface GmailMessage {\n id: string;\n threadId: string;\n labelIds: string[];\n snippet: string;\n payload?: {\n headers: Array<{ name: string; value: string }>;\n body?: { data?: string; size: number };\n parts?: Array<{\n mimeType: string;\n body?: { data?: string; size: number };\n }>;\n };\n internalDate: string;\n}\n\nexport interface GmailMessageList {\n messages: Array<{ id: string; threadId: string }>;\n nextPageToken?: string;\n resultSizeEstimate: number;\n}\n\nexport interface SendEmailOptions {\n to: string | string[];\n subject: string;\n body: string;\n cc?: string | string[];\n bcc?: string | string[];\n replyTo?: string;\n isHtml?: boolean;\n}\n\nconst tokenStoreAdapter = {\n async getTokens(serviceId: string): Promise<unknown> {\n return tokenStore.getToken(\"current-user\", serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ): Promise<void> {\n await tokenStore.setToken(\"current-user\", serviceId, tokens);\n },\n async clearTokens(serviceId: string): Promise<void> {\n await tokenStore.revokeToken(\"current-user\", serviceId);\n },\n // State methods not needed for API client\n async getState(): Promise<null> {\n return null;\n },\n async setState(): Promise<void> {},\n async clearState(): Promise<void> {},\n};\n\nconst gmailService = new OAuthService(gmailConfig, tokenStoreAdapter);\n\nexport function createGmailClient(): {\n isConnected(): Promise<boolean>;\n listMessages(options?: {\n maxResults?: number;\n query?: string;\n labelIds?: string[];\n pageToken?: string;\n }): Promise<GmailMessageList>;\n getMessage(messageId: string, format?: \"full\" | \"metadata\" | \"minimal\"): Promise<GmailMessage>;\n sendEmail(options: SendEmailOptions): Promise<{ id: string; threadId: string }>;\n searchEmails(query: string, maxResults?: number): Promise<GmailMessage[]>;\n getUnreadEmails(maxResults?: number): Promise<GmailMessage[]>;\n markAsRead(messageId: string): Promise<void>;\n archiveEmail(messageId: string): Promise<void>;\n} {\n async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n return gmailService.fetch<T>(endpoint, options);\n }\n\n function formatAddresses(addresses: string | string[] | undefined): string {\n if (!addresses) return \"\";\n return Array.isArray(addresses) ? addresses.join(\", \") : addresses;\n }\n\n return {\n async isConnected(): Promise<boolean> {\n const token = await gmailService.getAccessToken();\n return token !== null;\n },\n\n listMessages(\n options: {\n maxResults?: number;\n query?: string;\n labelIds?: string[];\n pageToken?: string;\n } = {},\n ): Promise<GmailMessageList> {\n const params = new URLSearchParams();\n\n if (options.maxResults) params.set(\"maxResults\", String(options.maxResults));\n if (options.query) params.set(\"q\", options.query);\n if (options.labelIds) params.set(\"labelIds\", options.labelIds.join(\",\"));\n if (options.pageToken) params.set(\"pageToken\", options.pageToken);\n\n const query = params.toString();\n return apiRequest<GmailMessageList>(`/users/me/messages${query ? `?${query}` : \"\"}`);\n },\n\n getMessage(messageId: string, format: \"full\" | \"metadata\" | \"minimal\" = \"full\"): Promise<GmailMessage> {\n return apiRequest<GmailMessage>(`/users/me/messages/${messageId}?format=${format}`);\n },\n\n sendEmail(options: SendEmailOptions): Promise<{ id: string; threadId: string }> {\n const toAddresses = formatAddresses(options.to);\n const ccAddresses = formatAddresses(options.cc);\n const bccAddresses = formatAddresses(options.bcc);\n\n const headers = [\n `To: ${toAddresses}`,\n `Subject: ${options.subject}`,\n options.isHtml ? \"Content-Type: text/html; charset=utf-8\" : \"Content-Type: text/plain; charset=utf-8\",\n ];\n\n if (ccAddresses) headers.push(`Cc: ${ccAddresses}`);\n if (bccAddresses) headers.push(`Bcc: ${bccAddresses}`);\n if (options.replyTo) headers.push(`Reply-To: ${options.replyTo}`);\n\n const email = `${headers.join(\"\\r\\n\")}\\r\\n\\r\\n${options.body}`;\n\n const encodedEmail = btoa(email).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n\n return apiRequest<{ id: string; threadId: string }>(\"/users/me/messages/send\", {\n method: \"POST\",\n body: JSON.stringify({ raw: encodedEmail }),\n });\n },\n\n async searchEmails(query: string, maxResults = 10): Promise<GmailMessage[]> {\n const list = await this.listMessages({ query, maxResults });\n if (!list.messages?.length) return [];\n\n return Promise.all(list.messages.map((m) => this.getMessage(m.id, \"metadata\")));\n },\n\n getUnreadEmails(maxResults = 10): Promise<GmailMessage[]> {\n return this.searchEmails(\"is:unread\", maxResults);\n },\n\n async markAsRead(messageId: string): Promise<void> {\n await apiRequest(`/users/me/messages/${messageId}/modify`, {\n method: \"POST\",\n body: JSON.stringify({ removeLabelIds: [\"UNREAD\"] }),\n });\n },\n\n async archiveEmail(messageId: string): Promise<void> {\n await apiRequest(`/users/me/messages/${messageId}/modify`, {\n method: \"POST\",\n body: JSON.stringify({ removeLabelIds: [\"INBOX\"] }),\n });\n },\n };\n}\n\nexport function parseEmailHeaders(\n headers: Array<{ name: string; value: string }>,\n): { from: string; to: string; subject: string; date: string } {\n function getHeader(name: string): string {\n const header = headers.find((h) => h.name.toLowerCase() === name.toLowerCase());\n return header?.value ?? \"\";\n }\n\n return {\n from: getHeader(\"From\"),\n to: getHeader(\"To\"),\n subject: getHeader(\"Subject\"),\n date: getHeader(\"Date\"),\n };\n}\n\nexport type GmailClient = ReturnType<typeof createGmailClient>;\n",
|
|
528
|
+
"tools/list-emails.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createGmailClient, parseEmailHeaders } from \"../../lib/gmail-client.ts\";\n\nexport default tool({\n id: \"list-emails\",\n description:\n \"List recent emails from Gmail inbox. Returns email subjects, senders, and snippets.\",\n inputSchema: z.object({\n maxResults: z\n .number()\n .min(1)\n .max(50)\n .default(10)\n .describe(\"Maximum number of emails to return\"),\n unreadOnly: z.boolean().default(false).describe(\"Only return unread emails\"),\n label: z\n .string()\n .optional()\n .describe(\"Filter by Gmail label (e.g., 'INBOX', 'IMPORTANT', 'STARRED')\"),\n }),\n execute: async ({ maxResults, unreadOnly, label }, context) => {\n // Default to \"current-user\" for development; in production, always pass userId from session\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const gmail = createGmailClient(userId);\n\n const query = unreadOnly ? \"is:unread\" : undefined;\n const labelIds = label ? [label] : undefined;\n\n const list = await gmail.listMessages({ maxResults, query, labelIds });\n\n if (!list.messages?.length) {\n return {\n emails: [],\n message: \"No emails found matching your criteria.\",\n };\n }\n\n const emails = await Promise.all(\n list.messages.map(async ({ id }: { id: string }) => {\n const message = await gmail.getMessage(id, \"metadata\");\n const headers = parseEmailHeaders(message.payload?.headers ?? []);\n\n return {\n id: message.id,\n threadId: message.threadId,\n from: headers.from,\n to: headers.to,\n subject: headers.subject,\n date: headers.date,\n snippet: message.snippet,\n isUnread: message.labelIds?.includes(\"UNREAD\") ?? false,\n isStarred: message.labelIds?.includes(\"STARRED\") ?? false,\n isImportant: message.labelIds?.includes(\"IMPORTANT\") ?? false,\n };\n }),\n );\n\n return {\n emails,\n count: emails.length,\n message: `Found ${emails.length} email(s).`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Gmail not connected. Please connect your Gmail account.\",\n connectUrl: \"/api/auth/gmail\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
529
|
+
"tools/send-email.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createGmailClient } from \"../../lib/gmail-client.ts\";\n\nfunction formatRecipients(value?: string | string[]): string | undefined {\n if (!value) return undefined;\n return Array.isArray(value) ? value.join(\", \") : value;\n}\n\nexport default tool({\n id: \"send-email\",\n description: \"Send an email via Gmail. Can send to multiple recipients with CC and BCC support.\",\n inputSchema: z.object({\n to: z.union([z.string().email(), z.array(z.string().email())]).describe(\"Email recipient(s)\"),\n subject: z.string().min(1).describe(\"Email subject line\"),\n body: z.string().min(1).describe(\"Email body content\"),\n cc: z\n .union([z.string().email(), z.array(z.string().email())])\n .optional()\n .describe(\"CC recipient(s)\"),\n bcc: z\n .union([z.string().email(), z.array(z.string().email())])\n .optional()\n .describe(\"BCC recipient(s)\"),\n isHtml: z.boolean().default(false).describe(\"Whether the body contains HTML\"),\n }),\n execute: async ({ to, subject, body, cc, bcc, isHtml }, context) => {\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const gmail = createGmailClient(userId);\n\n const result = await gmail.sendEmail({\n to,\n subject,\n body,\n cc,\n bcc,\n isHtml,\n });\n\n const toFormatted = formatRecipients(to)!;\n\n return {\n success: true,\n messageId: result.id,\n threadId: result.threadId,\n message: `Email sent successfully to ${toFormatted}.`,\n details: {\n to: toFormatted,\n subject,\n cc: formatRecipients(cc),\n bcc: formatRecipients(bcc),\n },\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Gmail not connected. Please connect your Gmail account.\",\n connectUrl: \"/api/auth/gmail\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
530
|
+
"tools/search-emails.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createGmailClient, parseEmailHeaders } from \"../../lib/gmail-client.ts\";\n\nexport default tool({\n id: \"search-emails\",\n description:\n \"Search emails using Gmail's search syntax. Supports queries like 'from:person@email.com', 'subject:meeting', 'after:2024/01/01', etc.\",\n inputSchema: z.object({\n query: z\n .string()\n .min(1)\n .describe(\n \"Search query using Gmail search syntax (e.g., 'from:boss@company.com subject:urgent')\",\n ),\n maxResults: z\n .number()\n .min(1)\n .max(50)\n .default(10)\n .describe(\"Maximum number of results to return\"),\n }),\n execute: async ({ query, maxResults }, context) => {\n const userId = context?.userId ?? \"current-user\";\n\n try {\n const gmail = createGmailClient(userId);\n\n const list = await gmail.listMessages({ query, maxResults });\n\n if (!list.messages?.length) {\n return {\n emails: [],\n query,\n message: `No emails found matching: \"${query}\"`,\n searchTips: [\n \"from:email@example.com - Search by sender\",\n \"to:email@example.com - Search by recipient\",\n \"subject:keywords - Search in subject\",\n \"after:YYYY/MM/DD - Emails after date\",\n \"before:YYYY/MM/DD - Emails before date\",\n \"is:unread - Unread emails only\",\n \"has:attachment - Emails with attachments\",\n ],\n };\n }\n\n const emails = await Promise.all(\n list.messages.map(async ({ id }: { id: string }) => {\n const message = await gmail.getMessage(id, \"metadata\");\n const headers = parseEmailHeaders(message.payload?.headers ?? []);\n\n return {\n id: message.id,\n threadId: message.threadId,\n from: headers.from,\n to: headers.to,\n subject: headers.subject,\n date: headers.date,\n snippet: message.snippet,\n isUnread: message.labelIds?.includes(\"UNREAD\") ?? false,\n labels: message.labelIds,\n };\n }),\n );\n\n return {\n emails,\n query,\n count: emails.length,\n message: `Found ${emails.length} email(s) matching: \"${query}\"`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not connected\")) {\n return {\n error: \"Gmail not connected. Please connect your Gmail account.\",\n connectUrl: \"/api/auth/gmail\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
531
|
+
"app/api/auth/gmail/route.ts": "import { createOAuthInitHandler, gmailConfig, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(gmailConfig, { tokenStore: memoryTokenStore });\n",
|
|
532
|
+
"app/api/auth/gmail/callback/route.ts": "import { createOAuthCallbackHandler, gmailConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) =>\n memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(gmailConfig, { tokenStore: hybridTokenStore });\n",
|
|
533
|
+
".env.example": "# =============================================================================\n# Gmail Integration Setup\n# =============================================================================\n#\n# STEP 1: Create a Google Cloud Project\n# Visit: https://console.cloud.google.com/projectcreate\n#\n# STEP 2: Enable the Gmail API\n# Visit: https://console.cloud.google.com/apis/library/gmail.googleapis.com\n# Click \"Enable\" to activate the Gmail API for your project\n#\n# STEP 3: Configure OAuth Consent Screen\n# Visit: https://console.cloud.google.com/apis/credentials/consent\n# - Choose \"External\" user type (or \"Internal\" for Workspace)\n# - Fill in app name, support email\n# - Add scopes: gmail.readonly, gmail.send, gmail.modify\n# - Add your email as a test user (required for development)\n#\n# STEP 4: Create OAuth Credentials\n# Visit: https://console.cloud.google.com/apis/credentials\n# - Click \"Create Credentials\" > \"OAuth client ID\"\n# - Application type: \"Web application\"\n# - Add Authorized redirect URI: http://localhost:3000/api/auth/gmail/callback\n# - Copy the Client ID and Client Secret below\n#\n# =============================================================================\n\nGOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com\nGOOGLE_CLIENT_SECRET=your-client-secret\n"
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
"integration:shopify": {
|
|
537
|
+
"files": {
|
|
538
|
+
"lib/shopify-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst SHOPIFY_SHOP_DOMAIN = process.env.SHOPIFY_SHOP_DOMAIN ?? \"shop.myshopify.com\";\nconst SHOPIFY_API_VERSION = \"2024-01\";\nconst SHOPIFY_BASE_URL = `https://${SHOPIFY_SHOP_DOMAIN}/admin/api/${SHOPIFY_API_VERSION}`;\n\ninterface ShopifyProduct {\n id: number;\n title: string;\n body_html: string;\n vendor: string;\n product_type: string;\n created_at: string;\n updated_at: string;\n published_at: string | null;\n status: string;\n tags: string;\n variants: Array<{\n id: number;\n title: string;\n price: string;\n sku: string;\n inventory_quantity: number;\n }>;\n images: Array<{\n id: number;\n src: string;\n alt: string | null;\n }>;\n}\n\ninterface ShopifyOrder {\n id: number;\n order_number: number;\n email: string;\n created_at: string;\n updated_at: string;\n total_price: string;\n subtotal_price: string;\n total_tax: string;\n currency: string;\n financial_status: string;\n fulfillment_status: string | null;\n customer: {\n id: number;\n email: string;\n first_name: string;\n last_name: string;\n } | null;\n line_items: Array<{\n id: number;\n title: string;\n quantity: number;\n price: string;\n sku: string;\n variant_title: string;\n }>;\n shipping_address: {\n address1: string;\n city: string;\n province: string;\n country: string;\n zip: string;\n } | null;\n}\n\ninterface ShopifyCustomer {\n id: number;\n email: string;\n first_name: string;\n last_name: string;\n phone: string | null;\n created_at: string;\n updated_at: string;\n orders_count: number;\n total_spent: string;\n tags: string;\n state: string;\n verified_email: boolean;\n addresses: Array<{\n id: number;\n address1: string;\n city: string;\n province: string;\n country: string;\n zip: string;\n default: boolean;\n }>;\n}\n\nfunction buildQuery(params: URLSearchParams): string {\n const query = params.toString();\n return query ? `?${query}` : \"\";\n}\n\nasync function shopifyFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Shopify. Please connect your account.\");\n }\n\n const response = await fetch(`${SHOPIFY_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n \"X-Shopify-Access-Token\": token,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as { errors?: string }));\n throw new Error(\n `Shopify API error: ${response.status} ${error.errors ?? response.statusText}`,\n );\n }\n\n return response.json();\n}\n\nexport async function listProducts(options?: {\n limit?: number;\n status?: \"active\" | \"archived\" | \"draft\";\n productType?: string;\n}): Promise<ShopifyProduct[]> {\n const params = new URLSearchParams();\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n if (options?.status) params.set(\"status\", options.status);\n if (options?.productType) params.set(\"product_type\", options.productType);\n\n const response = await shopifyFetch<{ products: ShopifyProduct[] }>(\n `/products.json${buildQuery(params)}`,\n );\n return response.products;\n}\n\nexport async function getProduct(productId: number | string): Promise<ShopifyProduct> {\n const response = await shopifyFetch<{ product: ShopifyProduct }>(`/products/${productId}.json`);\n return response.product;\n}\n\nexport async function listOrders(options?: {\n limit?: number;\n status?: \"open\" | \"closed\" | \"cancelled\" | \"any\";\n financialStatus?: \"pending\" | \"authorized\" | \"paid\" | \"refunded\" | \"voided\";\n fulfillmentStatus?: \"shipped\" | \"partial\" | \"unshipped\" | \"any\" | \"unfulfilled\";\n}): Promise<ShopifyOrder[]> {\n const params = new URLSearchParams();\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n if (options?.status) params.set(\"status\", options.status);\n if (options?.financialStatus) params.set(\"financial_status\", options.financialStatus);\n if (options?.fulfillmentStatus) params.set(\"fulfillment_status\", options.fulfillmentStatus);\n\n const response = await shopifyFetch<{ orders: ShopifyOrder[] }>(\n `/orders.json${buildQuery(params)}`,\n );\n return response.orders;\n}\n\nexport async function getOrder(orderId: number | string): Promise<ShopifyOrder> {\n const response = await shopifyFetch<{ order: ShopifyOrder }>(`/orders/${orderId}.json`);\n return response.order;\n}\n\nexport async function listCustomers(options?: { limit?: number; query?: string }): Promise<ShopifyCustomer[]> {\n const params = new URLSearchParams();\n if (options?.limit) params.set(\"limit\", options.limit.toString());\n if (options?.query) params.set(\"query\", options.query);\n\n const response = await shopifyFetch<{ customers: ShopifyCustomer[] }>(\n `/customers.json${buildQuery(params)}`,\n );\n return response.customers;\n}\n\nexport async function getCustomer(customerId: number | string): Promise<ShopifyCustomer> {\n const response = await shopifyFetch<{ customer: ShopifyCustomer }>(\n `/customers/${customerId}.json`,\n );\n return response.customer;\n}\n\nexport async function getShopInfo(): Promise<{\n id: number;\n name: string;\n email: string;\n domain: string;\n currency: string;\n timezone: string;\n}> {\n const response = await shopifyFetch<{\n shop: {\n id: number;\n name: string;\n email: string;\n domain: string;\n currency: string;\n timezone: string;\n };\n }>(\"/shop.json\");\n\n return response.shop;\n}\n",
|
|
539
|
+
"tools/list-products.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listProducts } from \"../../lib/shopify-client.ts\";\n\nexport default tool({\n id: \"list-products\",\n description:\n \"List products from your Shopify store. Can filter by status and product type.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(250)\n .default(20)\n .describe(\"Maximum number of products to return\"),\n status: z\n .enum([\"active\", \"archived\", \"draft\"])\n .optional()\n .describe(\"Filter by product status\"),\n productType: z.string().optional().describe(\"Filter by product type\"),\n }),\n async execute({ limit, status, productType }) {\n const products = await listProducts({ limit, status, productType });\n\n return products.map((product) => ({\n id: product.id,\n title: product.title,\n vendor: product.vendor,\n productType: product.product_type,\n status: product.status,\n tags: product.tags,\n createdAt: product.created_at,\n variants: product.variants.map((variant) => ({\n id: variant.id,\n title: variant.title,\n price: variant.price,\n sku: variant.sku,\n inventoryQuantity: variant.inventory_quantity,\n })),\n images: product.images.map((image) => ({\n id: image.id,\n src: image.src,\n alt: image.alt,\n })),\n }));\n },\n});\n",
|
|
540
|
+
"tools/get-order.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getOrder } from \"../../lib/shopify-client.ts\";\n\nexport default tool({\n id: \"get-order\",\n description: \"Get details of a specific Shopify order by its ID.\",\n inputSchema: z.object({\n orderId: z.union([z.number(), z.string()]).describe(\"The ID of the order to retrieve\"),\n }),\n async execute({ orderId }) {\n const order = await getOrder(orderId);\n const customer = order.customer;\n const shippingAddress = order.shipping_address;\n\n return {\n id: order.id,\n orderNumber: order.order_number,\n email: order.email,\n createdAt: order.created_at,\n updatedAt: order.updated_at,\n totalPrice: order.total_price,\n subtotalPrice: order.subtotal_price,\n totalTax: order.total_tax,\n currency: order.currency,\n financialStatus: order.financial_status,\n fulfillmentStatus: order.fulfillment_status,\n customer: customer\n ? {\n id: customer.id,\n email: customer.email,\n firstName: customer.first_name,\n lastName: customer.last_name,\n }\n : null,\n lineItems: order.line_items.map((item) => ({\n id: item.id,\n title: item.title,\n quantity: item.quantity,\n price: item.price,\n sku: item.sku,\n variantTitle: item.variant_title,\n })),\n shippingAddress: shippingAddress\n ? {\n address1: shippingAddress.address1,\n city: shippingAddress.city,\n province: shippingAddress.province,\n country: shippingAddress.country,\n zip: shippingAddress.zip,\n }\n : null,\n };\n },\n});\n",
|
|
541
|
+
"tools/get-product.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getProduct } from \"../../lib/shopify-client.ts\";\n\nexport default tool({\n id: \"get-product\",\n description: \"Get details of a specific Shopify product by its ID.\",\n inputSchema: z.object({\n productId: z.union([z.number(), z.string()]).describe(\"The ID of the product to retrieve\"),\n }),\n async execute({ productId }) {\n const product = await getProduct(productId);\n\n return {\n id: product.id,\n title: product.title,\n bodyHtml: product.body_html,\n vendor: product.vendor,\n productType: product.product_type,\n status: product.status,\n tags: product.tags,\n createdAt: product.created_at,\n updatedAt: product.updated_at,\n publishedAt: product.published_at,\n variants: product.variants.map(({ id, title, price, sku, inventory_quantity }) => ({\n id,\n title,\n price,\n sku,\n inventoryQuantity: inventory_quantity,\n })),\n images: product.images.map(({ id, src, alt }) => ({\n id,\n src,\n alt,\n })),\n };\n },\n});\n",
|
|
542
|
+
"tools/list-customers.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listCustomers } from \"../../lib/shopify-client.ts\";\n\nexport default tool({\n id: \"list-customers\",\n description: \"List customers from your Shopify store. Can search by query string.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(250)\n .default(20)\n .describe(\"Maximum number of customers to return\"),\n query: z\n .string()\n .optional()\n .describe(\"Search query to filter customers (e.g., email, name)\"),\n }),\n async execute({ limit, query }) {\n const customers = await listCustomers({ limit, query });\n\n return customers.map((customer) => ({\n id: customer.id,\n email: customer.email,\n firstName: customer.first_name,\n lastName: customer.last_name,\n phone: customer.phone,\n createdAt: customer.created_at,\n updatedAt: customer.updated_at,\n ordersCount: customer.orders_count,\n totalSpent: customer.total_spent,\n tags: customer.tags,\n state: customer.state,\n verifiedEmail: customer.verified_email,\n addresses: customer.addresses.map((address) => ({\n id: address.id,\n address1: address.address1,\n city: address.city,\n province: address.province,\n country: address.country,\n zip: address.zip,\n default: address.default,\n })),\n }));\n },\n});\n",
|
|
543
|
+
"tools/list-orders.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listOrders } from \"../../lib/shopify-client.ts\";\n\nexport default tool({\n id: \"list-orders\",\n description:\n \"List orders from your Shopify store. Can filter by status, financial status, and fulfillment status.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(250)\n .default(20)\n .describe(\"Maximum number of orders to return\"),\n status: z\n .enum([\"open\", \"closed\", \"cancelled\", \"any\"])\n .optional()\n .describe(\"Filter by order status\"),\n financialStatus: z\n .enum([\"pending\", \"authorized\", \"paid\", \"refunded\", \"voided\"])\n .optional()\n .describe(\"Filter by financial status\"),\n fulfillmentStatus: z\n .enum([\"shipped\", \"partial\", \"unshipped\", \"any\", \"unfulfilled\"])\n .optional()\n .describe(\"Filter by fulfillment status\"),\n }),\n async execute({ limit, status, financialStatus, fulfillmentStatus }) {\n const orders = await listOrders({ limit, status, financialStatus, fulfillmentStatus });\n\n return orders.map((order) => {\n const customer = order.customer\n ? {\n id: order.customer.id,\n email: order.customer.email,\n firstName: order.customer.first_name,\n lastName: order.customer.last_name,\n }\n : null;\n\n const shippingAddress = order.shipping_address\n ? {\n address1: order.shipping_address.address1,\n city: order.shipping_address.city,\n province: order.shipping_address.province,\n country: order.shipping_address.country,\n zip: order.shipping_address.zip,\n }\n : null;\n\n return {\n id: order.id,\n orderNumber: order.order_number,\n email: order.email,\n createdAt: order.created_at,\n totalPrice: order.total_price,\n subtotalPrice: order.subtotal_price,\n totalTax: order.total_tax,\n currency: order.currency,\n financialStatus: order.financial_status,\n fulfillmentStatus: order.fulfillment_status,\n customer,\n lineItems: order.line_items.map((item) => ({\n id: item.id,\n title: item.title,\n quantity: item.quantity,\n price: item.price,\n sku: item.sku,\n variantTitle: item.variant_title,\n })),\n shippingAddress,\n };\n });\n },\n});\n",
|
|
544
|
+
"app/api/auth/shopify/route.ts": "import { createOAuthInitHandler, shopifyConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(shopifyConfig);\n",
|
|
545
|
+
"app/api/auth/shopify/callback/route.ts": "import { createOAuthCallbackHandler, memoryTokenStore, shopifyConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(shopifyConfig, { tokenStore: hybridTokenStore });\n",
|
|
546
|
+
".env.example": "# Shopify OAuth Configuration\n# Get your credentials from https://partners.shopify.com\nSHOPIFY_CLIENT_ID=your-client-id\nSHOPIFY_CLIENT_SECRET=your-client-secret\nSHOPIFY_SHOP_DOMAIN=mystore.myshopify.com\n"
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
"integration:sharepoint": {
|
|
550
|
+
"files": {
|
|
551
|
+
"lib/sharepoint-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst GRAPH_BASE_URL = \"https://graph.microsoft.com/v1.0\";\n\nexport interface SharePointSite {\n id: string;\n name: string;\n displayName: string;\n description?: string;\n webUrl: string;\n createdDateTime: string;\n lastModifiedDateTime: string;\n siteCollection?: {\n hostname: string;\n };\n}\n\nexport interface SharePointDrive {\n id: string;\n name: string;\n description?: string;\n driveType: string;\n createdDateTime: string;\n lastModifiedDateTime: string;\n webUrl: string;\n quota?: {\n total: number;\n used: number;\n remaining: number;\n };\n}\n\nexport interface SharePointFile {\n id: string;\n name: string;\n size: number;\n createdDateTime: string;\n lastModifiedDateTime: string;\n webUrl: string;\n file?: {\n mimeType: string;\n hashes?: {\n sha1Hash?: string;\n quickXorHash?: string;\n };\n };\n folder?: {\n childCount: number;\n };\n parentReference?: {\n driveId: string;\n id: string;\n path: string;\n };\n createdBy?: {\n user?: {\n displayName: string;\n email?: string;\n };\n };\n lastModifiedBy?: {\n user?: {\n displayName: string;\n email?: string;\n };\n };\n}\n\ninterface GraphResponse<T> {\n value: T[];\n \"@odata.nextLink\"?: string;\n}\n\nasync function requireAccessToken(): Promise<string> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Microsoft. Please connect your account.\");\n }\n return token;\n}\n\nasync function graphFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await requireAccessToken();\n\n const response = await fetch(`${GRAPH_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...(options.headers ?? {}),\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(\n `Microsoft Graph API error: ${response.status} ${error.error?.message || response.statusText}`,\n );\n }\n\n return response.json();\n}\n\nexport async function listSites(options?: {\n search?: string;\n limit?: number;\n}): Promise<SharePointSite[]> {\n const endpoint = options?.search\n ? `/sites?search=${encodeURIComponent(options.search)}`\n : \"/sites?search=*\";\n\n const response = await graphFetch<GraphResponse<SharePointSite>>(endpoint);\n const sites = response.value ?? [];\n\n return options?.limit ? sites.slice(0, options.limit) : sites;\n}\n\nexport function getSite(siteId: string): Promise<SharePointSite> {\n return graphFetch<SharePointSite>(`/sites/${siteId}`);\n}\n\nexport function getSiteByPath(hostname: string, sitePath: string): Promise<SharePointSite> {\n return graphFetch<SharePointSite>(`/sites/${hostname}:${sitePath}`);\n}\n\nexport async function listDrives(siteId: string): Promise<SharePointDrive[]> {\n const response = await graphFetch<GraphResponse<SharePointDrive>>(`/sites/${siteId}/drives`);\n return response.value ?? [];\n}\n\nexport function getDefaultDrive(siteId: string): Promise<SharePointDrive> {\n return graphFetch<SharePointDrive>(`/sites/${siteId}/drive`);\n}\n\nexport async function listFiles(\n siteId: string,\n driveId: string,\n folderId?: string,\n options?: {\n limit?: number;\n orderBy?: string;\n },\n): Promise<SharePointFile[]> {\n const baseEndpoint = folderId\n ? `/sites/${siteId}/drives/${driveId}/items/${folderId}/children`\n : `/sites/${siteId}/drives/${driveId}/root/children`;\n\n const params = new URLSearchParams();\n if (options?.orderBy) params.set(\"$orderby\", options.orderBy);\n if (options?.limit) params.set(\"$top\", String(options.limit));\n\n const endpoint = params.toString() ? `${baseEndpoint}?${params.toString()}` : baseEndpoint;\n\n const response = await graphFetch<GraphResponse<SharePointFile>>(endpoint);\n return response.value ?? [];\n}\n\nexport function getFile(siteId: string, driveId: string, itemId: string): Promise<SharePointFile> {\n return graphFetch<SharePointFile>(`/sites/${siteId}/drives/${driveId}/items/${itemId}`);\n}\n\nexport function getFileByPath(siteId: string, driveId: string, path: string): Promise<SharePointFile> {\n const encodedPath = encodeURIComponent(path);\n return graphFetch<SharePointFile>(`/sites/${siteId}/drives/${driveId}/root:/${encodedPath}`);\n}\n\nexport async function downloadFile(\n siteId: string,\n driveId: string,\n itemId: string,\n): Promise<ArrayBuffer> {\n const token = await requireAccessToken();\n\n await getFile(siteId, driveId, itemId);\n\n const downloadUrl = `${GRAPH_BASE_URL}/sites/${siteId}/drives/${driveId}/items/${itemId}/content`;\n const response = await fetch(downloadUrl, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to download file: ${response.statusText}`);\n }\n\n return response.arrayBuffer();\n}\n\nexport async function downloadFileAsText(\n siteId: string,\n driveId: string,\n itemId: string,\n): Promise<string> {\n const buffer = await downloadFile(siteId, driveId, itemId);\n return new TextDecoder().decode(buffer);\n}\n\nexport async function uploadFile(\n siteId: string,\n driveId: string,\n fileName: string,\n content: string | ArrayBuffer | Blob,\n folderId?: string,\n): Promise<SharePointFile> {\n const token = await requireAccessToken();\n\n const encodedFileName = encodeURIComponent(fileName);\n const endpoint = folderId\n ? `/sites/${siteId}/drives/${driveId}/items/${folderId}:/${encodedFileName}:/content`\n : `/sites/${siteId}/drives/${driveId}/root:/${encodedFileName}:/content`;\n\n let body: ArrayBuffer;\n if (typeof content === \"string\") {\n body = new TextEncoder().encode(content);\n } else if (content instanceof Blob) {\n body = await content.arrayBuffer();\n } else {\n body = content;\n }\n\n const response = await fetch(`${GRAPH_BASE_URL}${endpoint}`, {\n method: \"PUT\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/octet-stream\",\n },\n body,\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(`Failed to upload file: ${error.error?.message || response.statusText}`);\n }\n\n return response.json();\n}\n\nexport function createFolder(\n siteId: string,\n driveId: string,\n folderName: string,\n parentFolderId?: string,\n): Promise<SharePointFile> {\n const endpoint = parentFolderId\n ? `/sites/${siteId}/drives/${driveId}/items/${parentFolderId}/children`\n : `/sites/${siteId}/drives/${driveId}/root/children`;\n\n return graphFetch<SharePointFile>(endpoint, {\n method: \"POST\",\n body: JSON.stringify({\n name: folderName,\n folder: {},\n \"@microsoft.graph.conflictBehavior\": \"rename\",\n }),\n });\n}\n\nexport async function searchFiles(\n siteId: string,\n query: string,\n options?: {\n limit?: number;\n },\n): Promise<SharePointFile[]> {\n const baseEndpoint = `/sites/${siteId}/drive/root/search(q='${encodeURIComponent(query)}')`;\n const endpoint = options?.limit ? `${baseEndpoint}?$top=${options.limit}` : baseEndpoint;\n\n const response = await graphFetch<GraphResponse<SharePointFile>>(endpoint);\n return response.value ?? [];\n}\n\nexport async function deleteItem(siteId: string, driveId: string, itemId: string): Promise<void> {\n await graphFetch<void>(`/sites/${siteId}/drives/${driveId}/items/${itemId}`, { method: \"DELETE\" });\n}\n\nexport function moveItem(\n siteId: string,\n driveId: string,\n itemId: string,\n newParentId: string,\n newName?: string,\n): Promise<SharePointFile> {\n const body: { parentReference: { id: string }; name?: string } = {\n parentReference: { id: newParentId },\n ...(newName ? { name: newName } : {}),\n };\n\n return graphFetch<SharePointFile>(`/sites/${siteId}/drives/${driveId}/items/${itemId}`, {\n method: \"PATCH\",\n body: JSON.stringify(body),\n });\n}\n\nexport async function copyItem(\n siteId: string,\n driveId: string,\n itemId: string,\n newParentId: string,\n newName?: string,\n): Promise<void> {\n const body: { parentReference: { driveId: string; id: string }; name?: string } = {\n parentReference: { driveId, id: newParentId },\n ...(newName ? { name: newName } : {}),\n };\n\n await graphFetch<void>(`/sites/${siteId}/drives/${driveId}/items/${itemId}/copy`, {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n",
|
|
552
|
+
"tools/upload-file.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createFolder, uploadFile } from \"../../lib/sharepoint-client.ts\";\n\nexport default tool({\n id: \"upload-file\",\n description:\n \"Upload a file to a SharePoint document library. Can upload to root or a specific folder.\",\n inputSchema: z.object({\n siteId: z.string().describe(\"The ID of the SharePoint site\"),\n driveId: z.string().describe(\"The ID of the document library (drive) to upload to\"),\n fileName: z.string().describe(\"The name of the file to create (including extension)\"),\n content: z.string().describe(\"The content of the file to upload\"),\n folderId: z\n .string()\n .optional()\n .describe(\"Optional folder ID to upload into. If not provided, uploads to root.\"),\n createFolderIfNeeded: z\n .boolean()\n .default(false)\n .describe(\"If true and folderPath is provided, creates the folder if it does not exist\"),\n folderPath: z\n .string()\n .optional()\n .describe(\n 'Optional folder path (e.g., \"Documents/Projects\") to create if createFolderIfNeeded is true',\n ),\n }),\n async execute({\n siteId,\n driveId,\n fileName,\n content,\n folderId,\n createFolderIfNeeded,\n folderPath,\n }) {\n let targetFolderId = folderId;\n\n if (createFolderIfNeeded && folderPath && !folderId) {\n const folders = folderPath.split(\"/\").filter(Boolean);\n let currentFolderId: string | undefined;\n\n for (const folderName of folders) {\n try {\n const folder = await createFolder(siteId, driveId, folderName, currentFolderId);\n currentFolderId = folder.id;\n } catch (error) {\n console.warn(`Note: Could not create folder \"${folderName}\":`, error);\n }\n }\n\n targetFolderId = currentFolderId;\n }\n\n const file = await uploadFile(siteId, driveId, fileName, content, targetFolderId);\n\n return {\n id: file.id,\n name: file.name,\n size: file.size,\n sizeFormatted: formatBytes(file.size),\n mimeType: file.file?.mimeType,\n url: file.webUrl,\n created: file.createdDateTime,\n lastModified: file.lastModifiedDateTime,\n parentPath: file.parentReference?.path,\n message: `Successfully uploaded \"${fileName}\" to SharePoint`,\n };\n },\n});\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1024;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${Math.round((bytes / Math.pow(k, i)) * 100) / 100} ${sizes[i]}`;\n}\n",
|
|
553
|
+
"tools/get-file.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { downloadFileAsText, getFile } from \"../../lib/sharepoint-client.ts\";\n\nexport default tool({\n id: \"get-file\",\n description:\n \"Get detailed metadata and optionally download content of a file from SharePoint. Can retrieve text content for text-based files.\",\n inputSchema: z.object({\n siteId: z.string().describe(\"The ID of the SharePoint site\"),\n driveId: z.string().describe(\"The ID of the document library (drive)\"),\n itemId: z.string().describe(\"The ID of the file to retrieve\"),\n includeContent: z\n .boolean()\n .default(false)\n .describe(\n \"Whether to download and include the file content (only works for text-based files)\",\n ),\n contentMaxLength: z\n .number()\n .min(100)\n .max(100000)\n .default(50000)\n .describe(\"Maximum length of content to return if includeContent is true\"),\n }),\n async execute({ siteId, driveId, itemId, includeContent, contentMaxLength }) {\n const file = await getFile(siteId, driveId, itemId);\n\n const result: Record<string, unknown> = {\n id: file.id,\n name: file.name,\n size: file.size,\n sizeFormatted: formatBytes(file.size),\n mimeType: file.file?.mimeType,\n url: file.webUrl,\n created: file.createdDateTime,\n lastModified: file.lastModifiedDateTime,\n createdBy: {\n name: file.createdBy?.user?.displayName,\n email: file.createdBy?.user?.email,\n },\n lastModifiedBy: {\n name: file.lastModifiedBy?.user?.displayName,\n email: file.lastModifiedBy?.user?.email,\n },\n parentReference: {\n driveId: file.parentReference?.driveId,\n id: file.parentReference?.id,\n path: file.parentReference?.path,\n },\n hashes: file.file?.hashes,\n };\n\n if (!includeContent) return result;\n\n const mimeType = file.file?.mimeType;\n if (!mimeType) return result;\n\n const textMimeTypes = [\n \"text/\",\n \"application/json\",\n \"application/xml\",\n \"application/javascript\",\n \"application/typescript\",\n ];\n\n const isTextFile = textMimeTypes.some((type) => mimeType.startsWith(type));\n\n if (!isTextFile) {\n result.contentError = \"File is not a text-based file type\";\n return result;\n }\n\n if (file.size >= contentMaxLength) {\n result.contentError = `File size (${formatBytes(file.size)}) exceeds maximum content length`;\n return result;\n }\n\n try {\n const content = await downloadFileAsText(siteId, driveId, itemId);\n const truncated = content.length > contentMaxLength;\n\n result.content = truncated\n ? content.substring(0, contentMaxLength) + \"\\n\\n[Content truncated...]\"\n : content;\n result.contentTruncated = truncated;\n } catch (error) {\n result.contentError = `Failed to download content: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`;\n }\n\n return result;\n },\n});\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1024;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${Math.round((bytes / Math.pow(k, i)) * 100) / 100} ${sizes[i]}`;\n}\n",
|
|
554
|
+
"tools/list-files.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listFiles, searchFiles } from \"../../lib/sharepoint-client.ts\";\n\nexport default tool({\n id: \"list-files\",\n description:\n \"List files and folders in a SharePoint document library. Can list root level or a specific folder, or search across the entire library.\",\n inputSchema: z.object({\n siteId: z.string().describe(\"The ID of the SharePoint site\"),\n driveId: z.string().describe(\"The ID of the document library (drive)\"),\n folderId: z\n .string()\n .optional()\n .describe(\n \"Optional folder ID to list contents from. If not provided, lists root level.\",\n ),\n search: z\n .string()\n .optional()\n .describe(\n \"Optional search query to find files by name or content instead of listing\",\n ),\n orderBy: z\n .enum([\"name\", \"lastModifiedDateTime\", \"size\"])\n .optional()\n .describe(\"Sort order for results\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(50)\n .describe(\"Maximum number of items to return\"),\n }),\n async execute({ siteId, driveId, folderId, search, orderBy, limit }) {\n const files = search\n ? await searchFiles(siteId, search, { limit })\n : await listFiles(siteId, driveId, folderId, { limit, orderBy });\n\n return files.map((file) => ({\n id: file.id,\n name: file.name,\n type: file.folder ? \"folder\" : \"file\",\n size: file.size,\n sizeFormatted: formatBytes(file.size),\n mimeType: file.file?.mimeType,\n url: file.webUrl,\n created: file.createdDateTime,\n lastModified: file.lastModifiedDateTime,\n createdBy: file.createdBy?.user?.displayName,\n lastModifiedBy: file.lastModifiedBy?.user?.displayName,\n parentPath: file.parentReference?.path,\n childCount: file.folder?.childCount,\n }));\n },\n});\n\nfunction formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1024;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${Math.round((bytes / Math.pow(k, i)) * 100) / 100} ${sizes[i]}`;\n}\n",
|
|
555
|
+
"tools/list-sites.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listSites } from \"../../lib/sharepoint-client.ts\";\n\nexport default tool({\n id: \"list-sites\",\n description:\n \"List all SharePoint sites the user has access to. Returns site names, URLs, and IDs.\",\n inputSchema: z.object({\n search: z\n .string()\n .optional()\n .describe(\"Optional search query to filter sites by name or description\"),\n limit: z\n .number()\n .min(1)\n .max(50)\n .default(20)\n .describe(\"Maximum number of sites to return\"),\n }),\n async execute({ search, limit }) {\n const sites = await listSites({ search, limit });\n\n return sites.map((site) => ({\n id: site.id,\n name: site.displayName ?? site.name,\n description: site.description,\n url: site.webUrl,\n hostname: site.siteCollection?.hostname,\n created: site.createdDateTime,\n lastModified: site.lastModifiedDateTime,\n }));\n },\n});\n",
|
|
556
|
+
"tools/get-site.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getSite, listDrives } from \"../../lib/sharepoint-client.ts\";\n\nexport default tool({\n id: \"get-site\",\n description:\n \"Get detailed information about a specific SharePoint site including its document libraries (drives).\",\n inputSchema: z.object({\n siteId: z.string().describe(\"The ID of the SharePoint site to retrieve\"),\n includeDrives: z\n .boolean()\n .default(true)\n .describe(\"Whether to include the list of document libraries in the response\"),\n }),\n async execute({ siteId, includeDrives }): Promise<Record<string, unknown>> {\n const site = await getSite(siteId);\n\n const result: Record<string, unknown> = {\n id: site.id,\n name: site.displayName ?? site.name,\n description: site.description,\n url: site.webUrl,\n hostname: site.siteCollection?.hostname,\n created: site.createdDateTime,\n lastModified: site.lastModifiedDateTime,\n };\n\n if (!includeDrives) return result;\n\n const drives = await listDrives(siteId);\n result.documentLibraries = drives.map((drive) => {\n const quota = drive.quota;\n\n return {\n id: drive.id,\n name: drive.name,\n description: drive.description,\n type: drive.driveType,\n url: drive.webUrl,\n quota: quota\n ? {\n total: quota.total,\n used: quota.used,\n remaining: quota.remaining,\n percentUsed:\n quota.total > 0 ? Math.round((quota.used / quota.total) * 100) : 0,\n }\n : undefined,\n };\n });\n\n return result;\n },\n});\n",
|
|
557
|
+
"app/api/auth/sharepoint/route.ts": "import { createOAuthInitHandler, memoryTokenStore, sharePointConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(sharePointConfig, { tokenStore: memoryTokenStore });\n",
|
|
558
|
+
"app/api/auth/sharepoint/callback/route.ts": "import { createOAuthCallbackHandler, memoryTokenStore, sharePointConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState(state: string) {\n return memoryTokenStore.getState(state);\n },\n setState(state: { state: string; codeVerifier?: string; createdAt: number }) {\n return memoryTokenStore.setState(state);\n },\n clearState(state: string) {\n return memoryTokenStore.clearState(state);\n },\n};\n\nexport const GET = createOAuthCallbackHandler(sharePointConfig, { tokenStore: hybridTokenStore });\n"
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
"integration:asana": {
|
|
562
|
+
"files": {
|
|
563
|
+
"lib/asana-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst ASANA_BASE_URL = \"https://app.asana.com/api/1.0\";\n\ninterface AsanaResponse<T> {\n data: T;\n next_page?: { offset: string } | null;\n}\n\ninterface AsanaTask {\n gid: string;\n name: string;\n notes: string;\n completed: boolean;\n due_on: string | null;\n assignee: { gid: string; name: string } | null;\n projects: Array<{ gid: string; name: string }>;\n created_at: string;\n modified_at: string;\n}\n\ninterface AsanaProject {\n gid: string;\n name: string;\n notes: string;\n workspace: { gid: string; name: string };\n created_at: string;\n modified_at: string;\n}\n\ninterface AsanaWorkspace {\n gid: string;\n name: string;\n}\n\nasync function asanaFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Asana. Please connect your account.\");\n }\n\n const response = await fetch(`${ASANA_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (response.ok) {\n return response.json();\n }\n\n const error = await response.json().catch(() => ({} as any));\n const message = error?.errors?.[0]?.message ?? response.statusText;\n throw new Error(`Asana API error: ${response.status} ${message}`);\n}\n\nexport async function listWorkspaces(): Promise<AsanaWorkspace[]> {\n const { data } = await asanaFetch<AsanaResponse<AsanaWorkspace[]>>(\"/workspaces\");\n return data;\n}\n\nexport async function listProjects(workspaceGid: string): Promise<AsanaProject[]> {\n const { data } = await asanaFetch<AsanaResponse<AsanaProject[]>>(\n `/workspaces/${workspaceGid}/projects?opt_fields=name,notes,created_at,modified_at`,\n );\n return data;\n}\n\nexport async function listTasks(options: {\n projectGid?: string;\n assigneeGid?: string;\n workspaceGid?: string;\n completedSince?: string;\n}): Promise<AsanaTask[]> {\n const params = new URLSearchParams({\n opt_fields: \"name,notes,completed,due_on,assignee.name,projects.name,created_at,modified_at\",\n });\n\n if (options.completedSince) {\n params.set(\"completed_since\", options.completedSince);\n }\n\n let endpoint = \"/tasks\";\n if (options.projectGid) {\n endpoint = `/projects/${options.projectGid}/tasks`;\n } else if (options.assigneeGid && options.workspaceGid) {\n params.set(\"assignee\", options.assigneeGid);\n params.set(\"workspace\", options.workspaceGid);\n }\n\n const { data } = await asanaFetch<AsanaResponse<AsanaTask[]>>(`${endpoint}?${params}`);\n return data;\n}\n\nexport async function getTask(taskGid: string): Promise<AsanaTask> {\n const { data } = await asanaFetch<AsanaResponse<AsanaTask>>(\n `/tasks/${taskGid}?opt_fields=name,notes,completed,due_on,assignee.name,projects.name,created_at,modified_at`,\n );\n return data;\n}\n\nexport async function createTask(options: {\n projectGid: string;\n name: string;\n notes?: string;\n dueOn?: string;\n assigneeGid?: string;\n}): Promise<AsanaTask> {\n const body: Record<string, unknown> = {\n name: options.name,\n projects: [options.projectGid],\n ...(options.notes ? { notes: options.notes } : {}),\n ...(options.dueOn ? { due_on: options.dueOn } : {}),\n ...(options.assigneeGid ? { assignee: options.assigneeGid } : {}),\n };\n\n const { data } = await asanaFetch<AsanaResponse<AsanaTask>>(\"/tasks\", {\n method: \"POST\",\n body: JSON.stringify({ data: body }),\n });\n return data;\n}\n\nexport async function updateTask(\n taskGid: string,\n updates: {\n name?: string;\n notes?: string;\n completed?: boolean;\n dueOn?: string;\n assigneeGid?: string;\n },\n): Promise<AsanaTask> {\n const body: Record<string, unknown> = {};\n\n if (updates.name !== undefined) body.name = updates.name;\n if (updates.notes !== undefined) body.notes = updates.notes;\n if (updates.completed !== undefined) body.completed = updates.completed;\n if (updates.dueOn !== undefined) body.due_on = updates.dueOn;\n if (updates.assigneeGid !== undefined) body.assignee = updates.assigneeGid;\n\n const { data } = await asanaFetch<AsanaResponse<AsanaTask>>(`/tasks/${taskGid}`, {\n method: \"PUT\",\n body: JSON.stringify({ data: body }),\n });\n return data;\n}\n\nexport async function getMe(): Promise<{ gid: string; name: string; email: string }> {\n const { data } = await asanaFetch<AsanaResponse<{ gid: string; name: string; email: string }>>(\n \"/users/me\",\n );\n return data;\n}\n",
|
|
564
|
+
"tools/list-tasks.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getMe, listTasks, listWorkspaces } from \"../../lib/asana-client.ts\";\n\nexport default tool({\n id: \"list-tasks\",\n description:\n \"List tasks from Asana. Can filter by project or get tasks assigned to the current user.\",\n inputSchema: z.object({\n projectGid: z.string().optional().describe(\"Project GID to list tasks from\"),\n assignedToMe: z\n .boolean()\n .default(false)\n .describe(\"List tasks assigned to the current user\"),\n includeCompleted: z.boolean().default(false).describe(\"Include completed tasks\"),\n limit: z.number().min(1).max(50).default(20).describe(\"Maximum number of tasks to return\"),\n }),\n async execute({ projectGid, assignedToMe, includeCompleted, limit }) {\n const completedSince = includeCompleted ? undefined : \"now\";\n\n if (!assignedToMe && !projectGid) {\n return {\n tasks: [],\n message: \"Please specify either a projectGid or set assignedToMe to true\",\n };\n }\n\n let tasks;\n\n if (assignedToMe) {\n const me = await getMe();\n const workspaces = await listWorkspaces();\n\n if (workspaces.length === 0) {\n return { tasks: [], message: \"No workspaces found\" };\n }\n\n tasks = await listTasks({\n assigneeGid: me.gid,\n workspaceGid: workspaces[0].gid,\n completedSince,\n });\n } else {\n tasks = await listTasks({\n projectGid,\n completedSince,\n });\n }\n\n return tasks.slice(0, limit).map((task) => ({\n gid: task.gid,\n name: task.name,\n completed: task.completed,\n dueOn: task.due_on,\n assignee: task.assignee?.name,\n projects: task.projects.map((p) => p.name),\n }));\n },\n});\n",
|
|
565
|
+
"tools/get-task.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getTask } from \"../../lib/asana-client.ts\";\n\nexport default tool({\n id: \"get-task\",\n description: \"Get details of a specific Asana task by its GID.\",\n inputSchema: z.object({\n taskGid: z.string().describe(\"The GID of the task to retrieve\"),\n }),\n async execute({ taskGid }) {\n const task = await getTask(taskGid);\n\n return {\n gid: task.gid,\n name: task.name,\n notes: task.notes,\n completed: task.completed,\n dueOn: task.due_on,\n assignee: task.assignee?.name,\n projects: task.projects.map((project) => ({\n gid: project.gid,\n name: project.name,\n })),\n createdAt: task.created_at,\n modifiedAt: task.modified_at,\n };\n },\n});\n",
|
|
566
|
+
"tools/create-task.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createTask } from \"../../lib/asana-client.ts\";\n\nexport default tool({\n id: \"create-task\",\n description: \"Create a new task in an Asana project.\",\n inputSchema: z.object({\n projectGid: z.string().describe(\"The GID of the project to create the task in\"),\n name: z.string().describe(\"The name/title of the task\"),\n notes: z.string().optional().describe(\"Description or notes for the task\"),\n dueOn: z.string().optional().describe(\"Due date in YYYY-MM-DD format\"),\n assigneeGid: z.string().optional().describe(\"GID of the user to assign the task to\"),\n }),\n async execute({ projectGid, name, notes, dueOn, assigneeGid }) {\n const { gid, name: taskName, due_on, assignee } = await createTask({\n projectGid,\n name,\n notes,\n dueOn,\n assigneeGid,\n });\n\n return {\n success: true,\n task: {\n gid,\n name: taskName,\n dueOn: due_on,\n assignee: assignee?.name,\n },\n };\n },\n});\n",
|
|
567
|
+
"tools/update-task.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { updateTask } from \"../../lib/asana-client.ts\";\n\nexport default tool({\n id: \"update-task\",\n description: \"Update an existing Asana task.\",\n inputSchema: z.object({\n taskGid: z.string().describe(\"The GID of the task to update\"),\n name: z.string().optional().describe(\"New name/title for the task\"),\n notes: z.string().optional().describe(\"New description or notes\"),\n completed: z.boolean().optional().describe(\"Mark the task as completed or not\"),\n dueOn: z.string().optional().describe(\"New due date in YYYY-MM-DD format\"),\n assigneeGid: z.string().optional().describe(\"GID of the user to reassign the task to\"),\n }),\n async execute({ taskGid, ...updates }) {\n const task = await updateTask(taskGid, updates);\n\n return {\n success: true,\n task: {\n gid: task.gid,\n name: task.name,\n completed: task.completed,\n dueOn: task.due_on,\n assignee: task.assignee?.name,\n },\n };\n },\n});\n",
|
|
568
|
+
"tools/list-projects.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listProjects, listWorkspaces } from \"../../lib/asana-client.ts\";\n\nexport default tool({\n id: \"list-projects\",\n description: \"List all projects in the Asana workspace.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(50)\n .default(20)\n .describe(\"Maximum number of projects to return\"),\n }),\n async execute({ limit }) {\n const [workspace] = await listWorkspaces();\n\n if (!workspace) {\n return { projects: [], message: \"No workspaces found\" };\n }\n\n const projects = await listProjects(workspace.gid);\n\n return projects.slice(0, limit).map(({ gid, name, notes, created_at }) => ({\n gid,\n name,\n notes,\n createdAt: created_at,\n }));\n },\n});\n",
|
|
569
|
+
"app/api/auth/asana/route.ts": "import { asanaConfig, createOAuthInitHandler } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(asanaConfig);\n",
|
|
570
|
+
"app/api/auth/asana/callback/route.ts": "import { asanaConfig, createOAuthCallbackHandler, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(asanaConfig, { tokenStore: hybridTokenStore });\n",
|
|
571
|
+
".env.example": "# Asana OAuth Configuration\n# Get your credentials from https://app.asana.com/0/developer-console\nASANA_CLIENT_ID=your-client-id\nASANA_CLIENT_SECRET=your-client-secret\n"
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
"integration:notion": {
|
|
575
|
+
"files": {
|
|
576
|
+
"lib/notion-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst NOTION_API_VERSION = \"2022-06-28\";\nconst NOTION_BASE_URL = \"https://api.notion.com/v1\";\n\ninterface NotionResponse<T> {\n object: string;\n results?: T[];\n next_cursor?: string | null;\n has_more?: boolean;\n}\n\ninterface NotionPage {\n id: string;\n object: \"page\";\n created_time: string;\n last_edited_time: string;\n parent: { type: string; database_id?: string; page_id?: string };\n properties: Record<string, NotionProperty>;\n url: string;\n}\n\ninterface NotionDatabase {\n id: string;\n object: \"database\";\n title: Array<{ plain_text: string }>;\n properties: Record<string, { type: string }>;\n}\n\ninterface NotionBlock {\n id: string;\n type: string;\n [key: string]: unknown;\n}\n\ninterface NotionProperty {\n type: string;\n title?: Array<{ plain_text: string }>;\n rich_text?: Array<{ plain_text: string }>;\n [key: string]: unknown;\n}\n\nasync function notionFetch<T>(\n endpoint: string,\n options: RequestInit = {},\n): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Notion. Please connect your account.\");\n }\n\n const response = await fetch(`${NOTION_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Notion-Version\": NOTION_API_VERSION,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as { message?: string }));\n throw new Error(\n `Notion API error: ${response.status} ${error.message ?? response.statusText}`,\n );\n }\n\n return response.json();\n}\n\nexport async function searchNotion(\n query: string,\n options?: {\n filter?: { property: \"object\"; value: \"page\" | \"database\" };\n pageSize?: number;\n },\n): Promise<Array<NotionPage | NotionDatabase>> {\n const body: Record<string, unknown> = { query };\n\n if (options?.filter) body.filter = options.filter;\n if (options?.pageSize) body.page_size = options.pageSize;\n\n const response = await notionFetch<NotionResponse<NotionPage | NotionDatabase>>(\n \"/search\",\n { method: \"POST\", body: JSON.stringify(body) },\n );\n\n return response.results ?? [];\n}\n\nexport function getPage(pageId: string): Promise<NotionPage> {\n return notionFetch<NotionPage>(`/pages/${pageId}`);\n}\n\nexport async function getPageContent(pageId: string): Promise<NotionBlock[]> {\n const response = await notionFetch<NotionResponse<NotionBlock>>(\n `/blocks/${pageId}/children`,\n );\n return response.results ?? [];\n}\n\nexport async function queryDatabase(\n databaseId: string,\n options?: {\n filter?: Record<string, unknown>;\n sorts?: Array<{ property: string; direction: \"ascending\" | \"descending\" }>;\n pageSize?: number;\n },\n): Promise<NotionPage[]> {\n const body: Record<string, unknown> = {};\n\n if (options?.filter) body.filter = options.filter;\n if (options?.sorts) body.sorts = options.sorts;\n if (options?.pageSize) body.page_size = options.pageSize;\n\n const response = await notionFetch<NotionResponse<NotionPage>>(\n `/databases/${databaseId}/query`,\n { method: \"POST\", body: JSON.stringify(body) },\n );\n\n return response.results ?? [];\n}\n\nexport function createPage(options: {\n parentId: string;\n parentType: \"database\" | \"page\";\n title: string;\n content?: string;\n properties?: Record<string, unknown>;\n}): Promise<NotionPage> {\n const parent = options.parentType === \"database\"\n ? { database_id: options.parentId }\n : { page_id: options.parentId };\n\n const properties: Record<string, unknown> = options.properties ?? {};\n\n if (options.parentType === \"database\") {\n properties.title ??= { title: [{ text: { content: options.title } }] };\n }\n\n const children: Array<Record<string, unknown>> = [];\n\n if (options.parentType === \"page\") {\n children.push({\n object: \"block\",\n type: \"heading_1\",\n heading_1: {\n rich_text: [{ type: \"text\", text: { content: options.title } }],\n },\n });\n }\n\n if (options.content) {\n for (const paragraph of options.content.split(\"\\n\\n\")) {\n const trimmed = paragraph.trim();\n if (!trimmed) continue;\n\n children.push({\n object: \"block\",\n type: \"paragraph\",\n paragraph: {\n rich_text: [{ type: \"text\", text: { content: trimmed } }],\n },\n });\n }\n }\n\n return notionFetch<NotionPage>(\"/pages\", {\n method: \"POST\",\n body: JSON.stringify({\n parent,\n properties,\n children: children.length ? children : undefined,\n }),\n });\n}\n\nexport function extractPlainText(blocks: NotionBlock[]): string {\n const texts: string[] = [];\n\n for (const block of blocks) {\n const content = block[block.type] as { rich_text?: Array<{ plain_text: string }> } | undefined;\n const text = content?.rich_text?.map((t) => t.plain_text).join(\"\");\n if (text) texts.push(text);\n }\n\n return texts.join(\"\\n\\n\");\n}\n\nexport function getPageTitle(page: NotionPage): string {\n for (const prop of Object.values(page.properties)) {\n if (prop.type === \"title\" && prop.title) {\n return prop.title.map((t) => t.plain_text).join(\"\");\n }\n }\n return \"Untitled\";\n}\n",
|
|
577
|
+
"tools/create-page.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createPage, getPageTitle } from \"../../lib/notion-client.ts\";\n\nexport default tool({\n id: \"create-page\",\n description:\n \"Create a new page in Notion. Can create as a subpage of an existing page or as a new entry in a database.\",\n inputSchema: z.object({\n parentId: z.string().describe(\"The ID of the parent page or database\"),\n parentType: z.enum([\"page\", \"database\"]).describe(\"Whether the parent is a page or database\"),\n title: z.string().describe(\"Title of the new page\"),\n content: z\n .string()\n .optional()\n .describe(\n \"Initial content for the page (plain text, paragraphs separated by double newlines)\",\n ),\n }),\n async execute({ parentId, parentType, title, content }) {\n const page = await createPage({ parentId, parentType, title, content });\n\n return {\n id: page.id,\n title: getPageTitle(page),\n url: page.url,\n createdAt: page.created_time,\n };\n },\n});\n",
|
|
578
|
+
"tools/search-notion.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getPageTitle, searchNotion } from \"../../lib/notion-client.ts\";\n\nexport default tool({\n id: \"search-notion\",\n description:\n \"Search for pages and databases in the connected Notion workspace. Returns matching pages with their titles and IDs.\",\n inputSchema: z.object({\n query: z.string().describe(\"Search query to find pages or databases\"),\n type: z\n .enum([\"page\", \"database\", \"all\"])\n .default(\"all\")\n .describe(\"Type of objects to search for\"),\n limit: z\n .number()\n .min(1)\n .max(20)\n .default(10)\n .describe(\"Maximum number of results to return\"),\n }),\n async execute({ query, type, limit }) {\n const filter =\n type === \"all\" ? undefined : { property: \"object\", value: type };\n\n const results = await searchNotion(query, { filter, pageSize: limit });\n\n return results.map((item) => {\n if (item.object === \"page\") {\n return {\n id: item.id,\n type: \"page\",\n title: getPageTitle(item),\n url: item.url,\n lastEdited: item.last_edited_time,\n };\n }\n\n return {\n id: item.id,\n type: \"database\",\n title: item.title?.map((t: { plain_text: string }) => t.plain_text).join(\"\") ?? \"\",\n url: item.url,\n };\n });\n },\n});\n",
|
|
579
|
+
"tools/read-page.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { extractPlainText, getPage, getPageContent, getPageTitle } from \"../../lib/notion-client.ts\";\n\nexport default tool({\n id: \"read-page\",\n description: \"Read the content of a Notion page. Returns the page title and text content.\",\n inputSchema: z.object({\n pageId: z.string().describe(\"The ID of the Notion page to read\"),\n }),\n async execute({ pageId }) {\n const [page, blocks] = await Promise.all([getPage(pageId), getPageContent(pageId)]);\n\n return {\n id: page.id,\n title: getPageTitle(page),\n url: page.url,\n content: extractPlainText(blocks),\n lastEdited: page.last_edited_time,\n createdAt: page.created_time,\n };\n },\n});\n",
|
|
580
|
+
"tools/query-database.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getPageTitle, queryDatabase } from \"../../lib/notion-client.ts\";\n\nexport default tool({\n id: \"query-database\",\n description: \"Query a Notion database to retrieve entries. Supports filtering and sorting.\",\n inputSchema: z.object({\n databaseId: z.string().describe(\"The ID of the Notion database to query\"),\n sortProperty: z.string().optional().describe(\"Property name to sort by\"),\n sortDirection: z\n .enum([\"ascending\", \"descending\"])\n .default(\"descending\")\n .describe(\"Sort direction\"),\n limit: z.number().min(1).max(50).default(20).describe(\"Maximum number of results\"),\n }),\n async execute({ databaseId, sortProperty, sortDirection, limit }) {\n const results = await queryDatabase(databaseId, {\n sorts: sortProperty ? [{ property: sortProperty, direction: sortDirection }] : undefined,\n pageSize: limit,\n });\n\n return results.map((page) => {\n const properties: Record<string, string> = {};\n\n for (const [key, prop] of Object.entries(page.properties)) {\n if (prop.type === \"title\") {\n properties[key] = prop.title?.map((t) => t.plain_text).join(\"\") ?? \"\";\n continue;\n }\n\n if (prop.type === \"rich_text\") {\n properties[key] = prop.rich_text?.map((t) => t.plain_text).join(\"\") ?? \"\";\n }\n }\n\n return {\n id: page.id,\n title: getPageTitle(page),\n url: page.url,\n properties,\n lastEdited: page.last_edited_time,\n };\n });\n },\n});\n",
|
|
581
|
+
"app/api/auth/notion/route.ts": "import { createOAuthInitHandler, memoryTokenStore, notionConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(notionConfig, { tokenStore: memoryTokenStore });\n",
|
|
582
|
+
"app/api/auth/notion/callback/route.ts": "import { createOAuthCallbackHandler, memoryTokenStore, notionConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(notionConfig, { tokenStore: hybridTokenStore });\n",
|
|
583
|
+
".env.example": "# Notion Integration\n# Create an integration at https://www.notion.so/my-integrations\n# Make sure to enable \"Public Integration\" for OAuth\n\nNOTION_CLIENT_ID=your_client_id_here\nNOTION_CLIENT_SECRET=your_client_secret_here\n"
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
"integration:quickbooks": {
|
|
587
|
+
"files": {
|
|
588
|
+
"lib/quickbooks-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst QUICKBOOKS_BASE_URL = \"https://quickbooks.api.intuit.com/v3\";\n\nfunction getRealmId(): string {\n const realmId = process.env.QUICKBOOKS_REALM_ID;\n if (!realmId) {\n throw new Error(\"QUICKBOOKS_REALM_ID environment variable is required\");\n }\n return realmId;\n}\n\ninterface QuickBooksResponse<T> {\n QueryResponse?: {\n [key: string]: T[] | number | undefined;\n maxResults?: number;\n startPosition?: number;\n };\n Invoice?: T;\n Customer?: T;\n time?: string;\n}\n\ninterface QuickBooksInvoice {\n Id: string;\n DocNumber: string;\n TxnDate: string;\n DueDate?: string;\n TotalAmt: number;\n Balance: number;\n CustomerRef: {\n value: string;\n name: string;\n };\n Line: Array<{\n Id: string;\n LineNum: number;\n Description?: string;\n Amount: number;\n DetailType: string;\n SalesItemLineDetail?: {\n ItemRef: {\n value: string;\n name: string;\n };\n Qty?: number;\n UnitPrice?: number;\n };\n }>;\n EmailStatus?: string;\n BillEmail?: {\n Address: string;\n };\n TxnStatus?: string;\n MetaData?: {\n CreateTime: string;\n LastUpdatedTime: string;\n };\n}\n\ninterface QuickBooksCustomer {\n Id: string;\n DisplayName: string;\n CompanyName?: string;\n GivenName?: string;\n FamilyName?: string;\n PrimaryEmailAddr?: {\n Address: string;\n };\n PrimaryPhone?: {\n FreeFormNumber: string;\n };\n BillAddr?: {\n Line1?: string;\n City?: string;\n CountrySubDivisionCode?: string;\n PostalCode?: string;\n };\n Balance: number;\n Active: boolean;\n MetaData?: {\n CreateTime: string;\n LastUpdatedTime: string;\n };\n}\n\nasync function quickbooksFetch<T>(\n endpoint: string,\n options: RequestInit = {},\n): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with QuickBooks. Please connect your account.\");\n }\n\n const url = `${QUICKBOOKS_BASE_URL}/company/${getRealmId()}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n const message = error.Fault?.Error?.[0]?.Message ?? response.statusText;\n throw new Error(`QuickBooks API error: ${response.status} ${message}`);\n }\n\n return response.json();\n}\n\nexport async function listInvoices(options?: {\n customerId?: string;\n maxResults?: number;\n}): Promise<QuickBooksInvoice[]> {\n const maxResults = options?.maxResults ?? 100;\n\n const query = options?.customerId\n ? `SELECT * FROM Invoice WHERE CustomerRef = '${options.customerId}' MAXRESULTS ${maxResults}`\n : `SELECT * FROM Invoice MAXRESULTS ${maxResults}`;\n\n const response = await quickbooksFetch<QuickBooksResponse<QuickBooksInvoice>>(\n `/query?query=${encodeURIComponent(query)}`,\n );\n\n return response.QueryResponse?.Invoice ?? [];\n}\n\nexport async function getInvoice(invoiceId: string): Promise<QuickBooksInvoice> {\n const response = await quickbooksFetch<QuickBooksResponse<QuickBooksInvoice>>(\n `/invoice/${invoiceId}`,\n );\n\n const invoice = response.Invoice;\n if (!invoice) {\n throw new Error(`Invoice ${invoiceId} not found`);\n }\n\n return invoice;\n}\n\nexport async function createInvoice(options: {\n customerId: string;\n lineItems: Array<{\n description?: string;\n amount: number;\n itemId?: string;\n quantity?: number;\n unitPrice?: number;\n }>;\n txnDate?: string;\n dueDate?: string;\n customerMemo?: string;\n}): Promise<QuickBooksInvoice> {\n const lines = options.lineItems.map((item, index) => {\n const line: Record<string, unknown> = {\n LineNum: index + 1,\n Amount: item.amount,\n DetailType: \"SalesItemLineDetail\",\n };\n\n if (item.description) {\n line.Description = item.description;\n }\n\n if (item.itemId) {\n line.SalesItemLineDetail = {\n ItemRef: { value: item.itemId },\n Qty: item.quantity ?? 1,\n UnitPrice: item.unitPrice ?? item.amount,\n };\n }\n\n return line;\n });\n\n const invoiceData: Record<string, unknown> = {\n CustomerRef: { value: options.customerId },\n Line: lines,\n ...(options.txnDate ? { TxnDate: options.txnDate } : {}),\n ...(options.dueDate ? { DueDate: options.dueDate } : {}),\n ...(options.customerMemo\n ? { CustomerMemo: { value: options.customerMemo } }\n : {}),\n };\n\n const response = await quickbooksFetch<QuickBooksResponse<QuickBooksInvoice>>(\n \"/invoice\",\n {\n method: \"POST\",\n body: JSON.stringify(invoiceData),\n },\n );\n\n const invoice = response.Invoice;\n if (!invoice) {\n throw new Error(\"Failed to create invoice\");\n }\n\n return invoice;\n}\n\nexport async function listCustomers(options?: {\n maxResults?: number;\n active?: boolean;\n}): Promise<QuickBooksCustomer[]> {\n const maxResults = options?.maxResults ?? 100;\n\n const query =\n options?.active === undefined\n ? `SELECT * FROM Customer MAXRESULTS ${maxResults}`\n : `SELECT * FROM Customer WHERE Active = ${options.active} MAXRESULTS ${maxResults}`;\n\n const response = await quickbooksFetch<QuickBooksResponse<QuickBooksCustomer>>(\n `/query?query=${encodeURIComponent(query)}`,\n );\n\n return response.QueryResponse?.Customer ?? [];\n}\n\nexport async function getCustomer(customerId: string): Promise<QuickBooksCustomer> {\n const response = await quickbooksFetch<QuickBooksResponse<QuickBooksCustomer>>(\n `/customer/${customerId}`,\n );\n\n const customer = response.Customer;\n if (!customer) {\n throw new Error(`Customer ${customerId} not found`);\n }\n\n return customer;\n}\n",
|
|
589
|
+
"tools/list-invoices.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listInvoices } from \"../../lib/quickbooks-client.ts\";\n\nexport default tool({\n id: \"list-invoices\",\n description: \"List invoices from QuickBooks. Can optionally filter by customer ID.\",\n inputSchema: z.object({\n customerId: z.string().optional().describe(\"Customer ID to filter invoices by\"),\n maxResults: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of invoices to return\"),\n }),\n async execute({ customerId, maxResults }) {\n const invoices = await listInvoices({ customerId, maxResults });\n\n return invoices.map((invoice) => ({\n id: invoice.Id,\n docNumber: invoice.DocNumber,\n txnDate: invoice.TxnDate,\n dueDate: invoice.DueDate,\n totalAmount: invoice.TotalAmt,\n balance: invoice.Balance,\n customer: {\n id: invoice.CustomerRef.value,\n name: invoice.CustomerRef.name,\n },\n status: invoice.TxnStatus,\n emailStatus: invoice.EmailStatus,\n lineItems: invoice.Line.map((line) => ({\n description: line.Description,\n amount: line.Amount,\n quantity: line.SalesItemLineDetail?.Qty,\n unitPrice: line.SalesItemLineDetail?.UnitPrice,\n })),\n }));\n },\n});\n",
|
|
590
|
+
"tools/list-customers.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listCustomers } from \"../../lib/quickbooks-client.ts\";\n\nexport default tool({\n id: \"list-customers\",\n description:\n \"List customers from QuickBooks. Can optionally filter by active status.\",\n inputSchema: z.object({\n active: z\n .boolean()\n .optional()\n .describe(\n \"Filter by active status (true for active, false for inactive)\",\n ),\n maxResults: z\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of customers to return\"),\n }),\n async execute({ active, maxResults }) {\n const customers = await listCustomers({ active, maxResults });\n\n return customers.map((customer) => {\n const billAddr = customer.BillAddr;\n\n return {\n id: customer.Id,\n displayName: customer.DisplayName,\n companyName: customer.CompanyName,\n givenName: customer.GivenName,\n familyName: customer.FamilyName,\n email: customer.PrimaryEmailAddr?.Address,\n phone: customer.PrimaryPhone?.FreeFormNumber,\n address: billAddr\n ? {\n line1: billAddr.Line1,\n city: billAddr.City,\n state: billAddr.CountrySubDivisionCode,\n postalCode: billAddr.PostalCode,\n }\n : undefined,\n balance: customer.Balance,\n active: customer.Active,\n };\n });\n },\n});\n",
|
|
591
|
+
"tools/get-invoice.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getInvoice } from \"../../lib/quickbooks-client.ts\";\n\nexport default tool({\n id: \"get-invoice\",\n description: \"Get details of a specific QuickBooks invoice by its ID.\",\n inputSchema: z.object({\n invoiceId: z.string().describe(\"The ID of the invoice to retrieve\"),\n }),\n async execute({ invoiceId }) {\n const invoice = await getInvoice(invoiceId);\n\n return {\n id: invoice.Id,\n docNumber: invoice.DocNumber,\n txnDate: invoice.TxnDate,\n dueDate: invoice.DueDate,\n totalAmount: invoice.TotalAmt,\n balance: invoice.Balance,\n customer: {\n id: invoice.CustomerRef.value,\n name: invoice.CustomerRef.name,\n },\n status: invoice.TxnStatus,\n emailStatus: invoice.EmailStatus,\n billEmail: invoice.BillEmail?.Address,\n lineItems: invoice.Line.map((line) => {\n const salesItemLineDetail = line.SalesItemLineDetail;\n\n return {\n id: line.Id,\n lineNum: line.LineNum,\n description: line.Description,\n amount: line.Amount,\n detailType: line.DetailType,\n salesItemLineDetail: salesItemLineDetail\n ? {\n itemName: salesItemLineDetail.ItemRef.name,\n quantity: salesItemLineDetail.Qty,\n unitPrice: salesItemLineDetail.UnitPrice,\n }\n : undefined,\n };\n }),\n metadata: {\n createTime: invoice.MetaData?.CreateTime,\n lastUpdatedTime: invoice.MetaData?.LastUpdatedTime,\n },\n };\n },\n});\n",
|
|
592
|
+
"tools/get-customer.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getCustomer } from \"../../lib/quickbooks-client.ts\";\n\nexport default tool({\n id: \"get-customer\",\n description: \"Get details of a specific QuickBooks customer by their ID.\",\n inputSchema: z.object({\n customerId: z.string().describe(\"The ID of the customer to retrieve\"),\n }),\n async execute({ customerId }) {\n const customer = await getCustomer(customerId);\n const billAddr = customer.BillAddr;\n\n return {\n id: customer.Id,\n displayName: customer.DisplayName,\n companyName: customer.CompanyName,\n givenName: customer.GivenName,\n familyName: customer.FamilyName,\n email: customer.PrimaryEmailAddr?.Address,\n phone: customer.PrimaryPhone?.FreeFormNumber,\n address: billAddr\n ? {\n line1: billAddr.Line1,\n city: billAddr.City,\n state: billAddr.CountrySubDivisionCode,\n postalCode: billAddr.PostalCode,\n }\n : undefined,\n balance: customer.Balance,\n active: customer.Active,\n metadata: {\n createTime: customer.MetaData?.CreateTime,\n lastUpdatedTime: customer.MetaData?.LastUpdatedTime,\n },\n };\n },\n});\n",
|
|
593
|
+
"tools/create-invoice.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createInvoice } from \"../../lib/quickbooks-client.ts\";\n\nexport default tool({\n id: \"create-invoice\",\n description: \"Create a new invoice in QuickBooks.\",\n inputSchema: z.object({\n customerId: z.string().describe(\"The ID of the customer to invoice\"),\n lineItems: z\n .array(\n z.object({\n description: z.string().optional().describe(\"Description of the line item\"),\n amount: z.number().describe(\"Total amount for this line item\"),\n itemId: z.string().optional().describe(\"QuickBooks item/service ID\"),\n quantity: z.number().optional().describe(\"Quantity of items\"),\n unitPrice: z.number().optional().describe(\"Price per unit\"),\n }),\n )\n .describe(\"Line items for the invoice\"),\n txnDate: z.string().optional().describe(\"Transaction date in YYYY-MM-DD format\"),\n dueDate: z.string().optional().describe(\"Due date in YYYY-MM-DD format\"),\n customerMemo: z.string().optional().describe(\"Memo/note for the customer\"),\n }),\n async execute({ customerId, lineItems, txnDate, dueDate, customerMemo }) {\n const invoice = await createInvoice({\n customerId,\n lineItems,\n txnDate,\n dueDate,\n customerMemo,\n });\n\n const { CustomerRef } = invoice;\n\n return {\n success: true,\n invoice: {\n id: invoice.Id,\n docNumber: invoice.DocNumber,\n txnDate: invoice.TxnDate,\n dueDate: invoice.DueDate,\n totalAmount: invoice.TotalAmt,\n balance: invoice.Balance,\n customer: {\n id: CustomerRef.value,\n name: CustomerRef.name,\n },\n },\n };\n },\n});\n",
|
|
594
|
+
"app/api/auth/quickbooks/route.ts": "import { createOAuthInitHandler, quickbooksConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(quickbooksConfig);\n",
|
|
595
|
+
"app/api/auth/quickbooks/callback/route.ts": "/**\n * QuickBooks OAuth Callback\n *\n * Handles the OAuth callback from QuickBooks and stores the tokens.\n */\n\nimport { createOAuthCallbackHandler, memoryTokenStore, quickbooksConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState(state: string) {\n return memoryTokenStore.getState(state);\n },\n setState(state: { state: string; codeVerifier?: string; createdAt: number }) {\n return memoryTokenStore.setState(state);\n },\n clearState(state: string) {\n return memoryTokenStore.clearState(state);\n },\n};\n\nexport const GET = createOAuthCallbackHandler(quickbooksConfig, { tokenStore: hybridTokenStore });\n",
|
|
596
|
+
".env.example": "# QuickBooks OAuth Configuration\n# Get your credentials from https://developer.intuit.com/app/developer/myapps\nQUICKBOOKS_CLIENT_ID=your-client-id\nQUICKBOOKS_CLIENT_SECRET=your-client-secret\n"
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
"integration:snowflake": {
|
|
600
|
+
"files": {
|
|
601
|
+
"lib/snowflake-client.ts": "import {\n getSnowflakeAccount,\n getSnowflakeDatabase,\n getSnowflakePassword,\n getSnowflakeSchema,\n getSnowflakeUsername,\n getSnowflakeWarehouse,\n} from \"./token-store.ts\";\n\ninterface SnowflakeStatementResponse {\n statementHandle: string;\n statementStatusUrl: string;\n message?: string;\n code?: string;\n}\n\ninterface SnowflakeQueryResult {\n resultSetMetaData: {\n rowType: Array<{\n name: string;\n type: string;\n nullable: boolean;\n scale?: number;\n precision?: number;\n length?: number;\n }>;\n numRows: number;\n format?: string;\n partitionInfo?: Array<{\n rowCount: number;\n uncompressedSize: number;\n }>;\n };\n data: unknown[][];\n code?: string;\n message?: string;\n statementHandle?: string;\n statementStatusUrl?: string;\n}\n\ninterface SnowflakeQueryStatusResponse {\n message: string;\n code: string;\n statementHandle: string;\n statementStatusUrl: string;\n sqlText?: string;\n resultSetMetaData?: SnowflakeQueryResult[\"resultSetMetaData\"];\n data?: unknown[][];\n stats?: {\n numRowsInserted?: number;\n numRowsUpdated?: number;\n numRowsDeleted?: number;\n numDuplicateRowsUpdated?: number;\n };\n}\n\ninterface DatabaseInfo {\n name: string;\n created_on: string;\n owner: string;\n comment?: string;\n}\n\ninterface SchemaInfo {\n name: string;\n database_name: string;\n created_on: string;\n owner: string;\n comment?: string;\n}\n\ninterface TableInfo {\n name: string;\n database_name: string;\n schema_name: string;\n kind: string;\n created_on: string;\n row_count?: number;\n bytes?: number;\n owner: string;\n comment?: string;\n}\n\ninterface ColumnInfo {\n name: string;\n type: string;\n kind: string;\n null?: string;\n default?: string;\n primary_key?: string;\n unique_key?: string;\n check?: string;\n expression?: string;\n comment?: string;\n}\n\ninterface SnowflakeError extends Error {\n code?: string;\n sqlState?: string;\n}\n\nasync function snowflakeFetch<T>(\n endpoint: string,\n options: RequestInit = {},\n): Promise<T> {\n const account = getSnowflakeAccount();\n const username = getSnowflakeUsername();\n const password = getSnowflakePassword();\n\n const baseUrl = `https://${account}.snowflakecomputing.com/api/v2`;\n const authHeader = `Basic ${btoa(`${username}:${password}`)}`;\n\n const response = await fetch(`${baseUrl}${endpoint}`, {\n ...options,\n headers: {\n Authorization: authHeader,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-Snowflake-Authorization-Token-Type\": \"KEYPAIR_JWT\",\n ...options.headers,\n },\n });\n\n if (response.ok) {\n return await response.json();\n }\n\n const errorData = (await response.json().catch(() => ({}))) as Partial<\n SnowflakeError\n >;\n const errorMessage =\n errorData.message ??\n `Snowflake API error: ${response.status} ${response.statusText}`;\n\n const err: SnowflakeError = new Error(errorMessage);\n err.code = errorData.code;\n err.sqlState = errorData.sqlState;\n throw error;\n}\n\nasync function submitStatement(\n sqlText: string,\n database?: string,\n schema?: string,\n timeout?: number,\n async_exec = false,\n): Promise<SnowflakeStatementResponse | SnowflakeQueryResult> {\n const warehouse = getSnowflakeWarehouse();\n\n const requestBody = {\n statement: sqlText,\n warehouse,\n database: database ?? getSnowflakeDatabase(),\n schema: schema ?? getSnowflakeSchema(),\n timeout: timeout ?? 60,\n resultSetMetaData: { format: \"json\" },\n parameters: {},\n };\n\n const endpoint = async_exec ? \"/statements?async=true\" : \"/statements\";\n\n return await snowflakeFetch<SnowflakeStatementResponse | SnowflakeQueryResult>(\n endpoint,\n {\n method: \"POST\",\n body: JSON.stringify(requestBody),\n },\n );\n}\n\nexport async function getQueryStatus(\n statementHandle: string,\n): Promise<SnowflakeQueryStatusResponse> {\n return await snowflakeFetch<SnowflakeQueryStatusResponse>(\n `/statements/${statementHandle}`,\n );\n}\n\nexport async function cancelQuery(statementHandle: string): Promise<void> {\n await snowflakeFetch(`/statements/${statementHandle}/cancel`, {\n method: \"POST\",\n });\n}\n\nfunction transformResults(\n result: SnowflakeQueryResult,\n): Record<string, unknown>[] {\n if (result.data.length === 0) return [];\n\n const columns = result.resultSetMetaData.rowType.map((col) => col.name);\n\n return result.data.map((row) => {\n const obj: Record<string, unknown> = {};\n for (let i = 0; i < columns.length; i++) obj[columns[i]] = row[i];\n return obj;\n });\n}\n\nexport async function runQuery(\n sql: string,\n database?: string,\n schema?: string,\n options: {\n timeout?: number;\n async?: boolean;\n } = {},\n): Promise<{\n columns: Array<{ name: string; type: string; nullable: boolean }>;\n rows: Record<string, unknown>[];\n rowCount: number;\n statementHandle?: string;\n}> {\n const result = await submitStatement(\n sql,\n database,\n schema,\n options.timeout,\n options.async,\n );\n\n if (\"statementHandle\" in result && !(\"data\" in result)) {\n return {\n columns: [],\n rows: [],\n rowCount: 0,\n statementHandle: result.statementHandle,\n };\n }\n\n const queryResult = result as SnowflakeQueryResult;\n\n return {\n columns: queryResult.resultSetMetaData.rowType.map((col) => ({\n name: col.name,\n type: col.type,\n nullable: col.nullable,\n })),\n rows: transformResults(queryResult),\n rowCount: queryResult.resultSetMetaData.numRows,\n statementHandle: queryResult.statementHandle,\n };\n}\n\nexport async function listDatabases(): Promise<DatabaseInfo[]> {\n const result = await runQuery(\"SHOW DATABASES\");\n return result.rows as DatabaseInfo[];\n}\n\nexport async function listSchemas(database: string): Promise<SchemaInfo[]> {\n const result = await runQuery(`SHOW SCHEMAS IN DATABASE ${database}`);\n return result.rows as SchemaInfo[];\n}\n\nexport async function listTables(\n database: string,\n schema: string,\n): Promise<TableInfo[]> {\n const result = await runQuery(`SHOW TABLES IN ${database}.${schema}`);\n return result.rows as TableInfo[];\n}\n\nexport async function describeTable(\n database: string,\n schema: string,\n table: string,\n): Promise<{\n columns: ColumnInfo[];\n primaryKeys: string[];\n}> {\n const result = await runQuery(`DESCRIBE TABLE ${database}.${schema}.${table}`);\n\n const columns = result.rows as ColumnInfo[];\n const primaryKeys = columns\n .filter((col) => col.primary_key === \"Y\")\n .map((col) => col.name);\n\n return { columns, primaryKeys };\n}\n\nexport async function getTableRowCount(\n database: string,\n schema: string,\n table: string,\n): Promise<number> {\n const result = await runQuery(\n `SELECT COUNT(*) as count FROM ${database}.${schema}.${table}`,\n );\n\n const count = result.rows[0]?.count;\n return count == null ? 0 : Number(count);\n}\n\nexport async function getSessionInfo(): Promise<{\n version: string;\n warehouse: string;\n database?: string;\n schema?: string;\n user: string;\n role?: string;\n}> {\n const result = await runQuery(`\n SELECT\n CURRENT_VERSION() as version,\n CURRENT_WAREHOUSE() as warehouse,\n CURRENT_DATABASE() as database,\n CURRENT_SCHEMA() as schema,\n CURRENT_USER() as user,\n CURRENT_ROLE() as role\n `);\n\n const row = result.rows[0];\n if (!row) throw new Error(\"Failed to get session info\");\n\n return row as {\n version: string;\n warehouse: string;\n database?: string;\n schema?: string;\n user: string;\n role?: string;\n };\n}\n",
|
|
602
|
+
"tools/list-schemas.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listSchemas } from \"../../lib/snowflake-client.ts\";\n\nexport default tool({\n id: \"list-schemas\",\n description:\n \"List all schemas in a Snowflake database. Returns schema names, database names, creation dates, and owners.\",\n inputSchema: z.object({\n database: z\n .string()\n .describe(\"The name of the database to list schemas from\"),\n includeDetails: z\n .boolean()\n .default(true)\n .describe(\n \"Include detailed information like creation date, owner, and comments\",\n ),\n }),\n async execute({ database, includeDetails }) {\n const schemas = await listSchemas(database);\n\n if (!includeDetails) {\n return {\n database,\n count: schemas.length,\n schemas: schemas.map(({ name }) => name),\n };\n }\n\n return {\n database,\n count: schemas.length,\n schemas: schemas.map(\n ({ name, database_name, created_on, owner, comment }) => ({\n name,\n databaseName: database_name,\n createdOn: created_on,\n owner,\n comment: comment ?? null,\n }),\n ),\n };\n },\n});\n",
|
|
603
|
+
"tools/list-databases.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listDatabases } from \"../../lib/snowflake-client.ts\";\n\nexport default tool({\n id: \"list-databases\",\n description:\n \"List all databases in your Snowflake account. Returns database names, creation dates, owners, and comments.\",\n inputSchema: z.object({\n includeDetails: z\n .boolean()\n .default(true)\n .describe(\"Include detailed information like creation date, owner, and comments\"),\n }),\n async execute({ includeDetails }) {\n const databases = await listDatabases();\n\n const count = databases.length;\n\n if (!includeDetails) {\n return {\n count,\n databases: databases.map((db) => db.name),\n };\n }\n\n return {\n count,\n databases: databases.map((db) => ({\n name: db.name,\n createdOn: db.created_on,\n owner: db.owner,\n comment: db.comment || null,\n })),\n };\n },\n});\n",
|
|
604
|
+
"tools/run-query.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getQueryStatus, runQuery } from \"../../lib/snowflake-client.ts\";\n\nexport default tool({\n id: \"run-query\",\n description:\n \"Execute a SQL query against your Snowflake data warehouse. Supports SELECT, INSERT, UPDATE, DELETE, and other SQL operations.\",\n inputSchema: z.object({\n sql: z\n .string()\n .describe(\n \"The SQL query to execute. Can be SELECT, INSERT, UPDATE, DELETE, or DDL statements.\",\n ),\n database: z\n .string()\n .optional()\n .describe(\n \"The database to use for this query. If not specified, uses the default database.\",\n ),\n schema: z\n .string()\n .optional()\n .describe(\n \"The schema to use for this query. If not specified, uses the default schema.\",\n ),\n timeout: z\n .number()\n .min(1)\n .max(300)\n .default(60)\n .describe(\"Query timeout in seconds (1-300). Default is 60 seconds.\"),\n async: z\n .boolean()\n .default(false)\n .describe(\n \"Execute query asynchronously. If true, returns immediately with a statement handle to check status later.\",\n ),\n }),\n async execute({ sql, database, schema, timeout, async: asyncExec }) {\n const result = await runQuery(sql, database, schema, {\n timeout,\n async: asyncExec,\n });\n\n if (asyncExec && result.statementHandle) {\n return {\n status: \"submitted\",\n statementHandle: result.statementHandle,\n message:\n \"Query submitted for async execution. Use the statement handle to check status.\",\n };\n }\n\n return {\n status: \"completed\",\n sql,\n database: database ?? \"default\",\n schema: schema ?? \"PUBLIC\",\n columns: result.columns,\n rowCount: result.rowCount,\n rows: result.rows,\n statementHandle: result.statementHandle,\n };\n },\n});\n\nexport const checkQueryStatus = tool({\n id: \"check-query-status\",\n description:\n \"Check the status and retrieve results of an asynchronously executed query.\",\n inputSchema: z.object({\n statementHandle: z\n .string()\n .describe(\"The statement handle returned from an async query execution.\"),\n }),\n async execute({ statementHandle }) {\n const status = await getQueryStatus(statementHandle);\n\n if (status.code === \"090001\") {\n return {\n status: \"running\",\n message: status.message,\n statementHandle,\n };\n }\n\n if (status.code !== \"000000\" && status.code) {\n return {\n status: \"failed\",\n code: status.code,\n message: status.message,\n statementHandle,\n };\n }\n\n const rowType = status.resultSetMetaData?.rowType ?? [];\n const columns = rowType.map((col) => ({\n name: col.name,\n type: col.type,\n nullable: col.nullable,\n }));\n\n const rows =\n status.data && status.resultSetMetaData\n ? status.data.map((row) => {\n const obj: Record<string, unknown> = {};\n rowType.forEach((col, index) => {\n obj[col.name] = row[index];\n });\n return obj;\n })\n : [];\n\n return {\n status: \"completed\",\n columns,\n rowCount: status.resultSetMetaData?.numRows ?? 0,\n rows,\n stats: status.stats,\n statementHandle,\n };\n },\n});\n",
|
|
605
|
+
"tools/describe-table.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { describeTable, getTableRowCount } from \"../../lib/snowflake-client.ts\";\n\nexport default tool({\n id: \"describe-table\",\n description:\n \"Get detailed schema information about a specific table in Snowflake. Returns column names, data types, constraints, and table statistics.\",\n inputSchema: z.object({\n database: z.string().describe(\"The name of the database containing the table\"),\n schema: z\n .string()\n .default(\"PUBLIC\")\n .describe(\"The name of the schema containing the table. Defaults to PUBLIC.\"),\n table: z.string().describe(\"The name of the table to describe\"),\n includeRowCount: z\n .boolean()\n .default(false)\n .describe(\n \"Include the current row count for the table (may be slow for large tables)\",\n ),\n }),\n async execute({ database, schema, table, includeRowCount }) {\n const description = await describeTable(database, schema, table);\n\n let rowCount: number | null = null;\n if (includeRowCount) {\n try {\n rowCount = await getTableRowCount(database, schema, table);\n } catch {\n rowCount = null;\n }\n }\n\n return {\n database,\n schema,\n table,\n rowCount,\n primaryKeys: description.primaryKeys,\n columnCount: description.columns.length,\n columns: description.columns.map((col) => ({\n name: col.name,\n type: col.type,\n kind: col.kind,\n nullable: col.null === \"Y\",\n default: col.default || null,\n primaryKey: col.primary_key === \"Y\",\n uniqueKey: col.unique_key === \"Y\",\n check: col.check || null,\n expression: col.expression || null,\n comment: col.comment || null,\n })),\n };\n },\n});\n",
|
|
606
|
+
"tools/list-tables.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listTables } from \"../../lib/snowflake-client.ts\";\n\nexport default tool({\n id: \"list-tables\",\n description:\n \"List all tables in a Snowflake database schema. Returns table names, types, creation dates, row counts, and sizes.\",\n inputSchema: z.object({\n database: z.string().describe(\"The name of the database containing the schema\"),\n schema: z\n .string()\n .default(\"PUBLIC\")\n .describe(\"The name of the schema to list tables from. Defaults to PUBLIC.\"),\n includeDetails: z\n .boolean()\n .default(true)\n .describe(\n \"Include detailed information like creation date, row count, size, and owner\",\n ),\n }),\n async execute({ database, schema, includeDetails }) {\n const tables = await listTables(database, schema);\n\n const base = {\n database,\n schema,\n count: tables.length,\n };\n\n if (!includeDetails) {\n return {\n ...base,\n tables: tables.map((t) => t.name),\n };\n }\n\n return {\n ...base,\n tables: tables.map((t) => ({\n name: t.name,\n databaseName: t.database_name,\n schemaName: t.schema_name,\n kind: t.kind,\n createdOn: t.created_on,\n rowCount: t.row_count ?? null,\n bytes: t.bytes ?? null,\n owner: t.owner,\n comment: t.comment ?? null,\n })),\n };\n },\n});\n",
|
|
607
|
+
".env.example": "# Snowflake Integration\n# Get your account details from https://app.snowflake.com/\n\n# Your Snowflake account identifier (e.g., xy12345.us-east-1)\nSNOWFLAKE_ACCOUNT=xy12345.us-east-1\n\n# Authentication credentials\nSNOWFLAKE_USERNAME=your_username\nSNOWFLAKE_PASSWORD=your_password\n\n# Default warehouse for compute resources\nSNOWFLAKE_WAREHOUSE=COMPUTE_WH\n\n# Optional: Default database and schema\nSNOWFLAKE_DATABASE=your_database\nSNOWFLAKE_SCHEMA=PUBLIC\n"
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
"integration:drive": {
|
|
611
|
+
"files": {
|
|
612
|
+
"lib/drive-client.ts": "/**\n * Google Drive API Client\n *\n * Provides a type-safe interface to Google Drive API operations.\n */\n\nimport { getValidToken } from \"./oauth.ts\";\n\n// Helper for Cross-Platform environment access\nfunction getEnv(key: string): string | undefined {\n // @ts-ignore - Deno global\n if (typeof Deno !== \"undefined\") return Deno.env.get(key);\n // @ts-ignore - process global\n if (typeof process !== \"undefined\" && process.env) return process.env[key];\n return undefined;\n}\n\nconst DRIVE_API_BASE = \"https://www.googleapis.com/drive/v3\";\n\nexport interface DriveFile {\n id: string;\n name: string;\n mimeType: string;\n kind: string;\n createdTime: string;\n modifiedTime: string;\n size?: string;\n webViewLink?: string;\n webContentLink?: string;\n iconLink?: string;\n thumbnailLink?: string;\n parents?: string[];\n starred?: boolean;\n trashed?: boolean;\n shared?: boolean;\n owners?: Array<{\n displayName: string;\n emailAddress: string;\n photoLink?: string;\n }>;\n lastModifyingUser?: {\n displayName: string;\n emailAddress: string;\n photoLink?: string;\n };\n capabilities?: {\n canEdit?: boolean;\n canComment?: boolean;\n canShare?: boolean;\n canDelete?: boolean;\n canDownload?: boolean;\n };\n}\n\nexport interface DriveFileList {\n files: DriveFile[];\n nextPageToken?: string;\n incompleteSearch?: boolean;\n}\n\nexport interface CreateFolderOptions {\n name: string;\n parentId?: string;\n description?: string;\n}\n\nexport interface UploadFileOptions {\n name: string;\n content: string;\n mimeType: string;\n parentId?: string;\n description?: string;\n}\n\nexport interface ListFilesOptions {\n folderId?: string;\n pageSize?: number;\n pageToken?: string;\n orderBy?: string;\n query?: string;\n}\n\nexport interface SearchFilesOptions {\n query: string;\n pageSize?: number;\n pageToken?: string;\n orderBy?: string;\n}\n\n/**\n * Google Drive OAuth provider configuration\n */\nexport const driveOAuthProvider = {\n name: \"drive\",\n authorizationUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n tokenUrl: \"https://oauth2.googleapis.com/token\",\n clientId: getEnv(\"GOOGLE_CLIENT_ID\") ?? \"\",\n clientSecret: getEnv(\"GOOGLE_CLIENT_SECRET\") ?? \"\",\n scopes: [\n \"https://www.googleapis.com/auth/drive.readonly\",\n \"https://www.googleapis.com/auth/drive.file\",\n ],\n callbackPath: \"/api/auth/drive/callback\",\n};\n\n/**\n * Create a Drive client for a specific user\n */\nexport function createDriveClient(userId: string): {\n listFiles(options?: ListFilesOptions): Promise<DriveFileList>;\n getFile(fileId: string): Promise<DriveFile>;\n searchFiles(options: SearchFilesOptions): Promise<DriveFileList>;\n createFolder(options: CreateFolderOptions): Promise<DriveFile>;\n uploadFile(options: UploadFileOptions): Promise<DriveFile>;\n downloadFile(fileId: string): Promise<string>;\n deleteFile(fileId: string): Promise<void>;\n copyFile(fileId: string, name: string, parentId?: string): Promise<DriveFile>;\n updateFile(\n fileId: string,\n updates: { name?: string; description?: string; starred?: boolean },\n ): Promise<DriveFile>;\n} {\n async function getAccessToken(): Promise<string> {\n const token = await getValidToken(driveOAuthProvider, userId, \"drive\");\n if (!token) {\n throw new Error(\"Google Drive not connected. Please connect your Google account first.\");\n }\n return token;\n }\n\n async function driveApiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const accessToken = await getAccessToken();\n\n const response = await fetch(`${DRIVE_API_BASE}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Drive API error: ${response.status} - ${error}`);\n }\n\n return response.json();\n }\n\n function buildMetadata(options: {\n name: string;\n mimeType: string;\n parentId?: string;\n description?: string;\n }): Record<string, unknown> {\n const metadata: Record<string, unknown> = {\n name: options.name,\n mimeType: options.mimeType,\n };\n\n if (options.parentId) metadata.parents = [options.parentId];\n if (options.description) metadata.description = options.description;\n\n return metadata;\n }\n\n return {\n /**\n * List files in a folder (or root if no folderId provided)\n */\n async listFiles(options: ListFilesOptions = {}): Promise<DriveFileList> {\n const params = new URLSearchParams({\n fields:\n \"nextPageToken,incompleteSearch,files(id,name,mimeType,kind,createdTime,modifiedTime,size,webViewLink,webContentLink,iconLink,thumbnailLink,parents,starred,trashed,shared,owners,lastModifyingUser,capabilities)\",\n pageSize: String(options.pageSize ?? 100),\n orderBy: options.orderBy ?? \"modifiedTime desc\",\n });\n\n let query = \"trashed=false\";\n if (options.folderId) query += ` and '${options.folderId}' in parents`;\n if (options.query) query += ` and ${options.query}`;\n\n params.append(\"q\", query);\n if (options.pageToken) params.append(\"pageToken\", options.pageToken);\n\n return driveApiRequest<DriveFileList>(`/files?${params.toString()}`);\n },\n\n /**\n * Get metadata about a specific file\n */\n async getFile(fileId: string): Promise<DriveFile> {\n const params = new URLSearchParams({\n fields:\n \"id,name,mimeType,kind,createdTime,modifiedTime,size,webViewLink,webContentLink,iconLink,thumbnailLink,parents,starred,trashed,shared,owners,lastModifyingUser,capabilities\",\n });\n\n return driveApiRequest<DriveFile>(`/files/${fileId}?${params.toString()}`);\n },\n\n /**\n * Search for files using Drive query syntax\n */\n async searchFiles(options: SearchFilesOptions): Promise<DriveFileList> {\n const params = new URLSearchParams({\n fields:\n \"nextPageToken,incompleteSearch,files(id,name,mimeType,kind,createdTime,modifiedTime,size,webViewLink,webContentLink,iconLink,thumbnailLink,parents,starred,trashed)\",\n pageSize: String(options.pageSize ?? 100),\n q: `${options.query} and trashed=false`,\n orderBy: options.orderBy ?? \"modifiedTime desc\",\n });\n\n if (options.pageToken) params.append(\"pageToken\", options.pageToken);\n\n return driveApiRequest<DriveFileList>(`/files?${params.toString()}`);\n },\n\n /**\n * Create a new folder\n */\n async createFolder(options: CreateFolderOptions): Promise<DriveFile> {\n const metadata = buildMetadata({\n name: options.name,\n mimeType: \"application/vnd.google-apps.folder\",\n parentId: options.parentId,\n description: options.description,\n });\n\n return driveApiRequest<DriveFile>(\"/files\", {\n method: \"POST\",\n body: JSON.stringify(metadata),\n });\n },\n\n /**\n * Upload a text file to Drive\n */\n async uploadFile(options: UploadFileOptions): Promise<DriveFile> {\n const accessToken = await getAccessToken();\n\n const boundary = \"-------314159265358979323846\";\n const delimiter = `\\r\\n--${boundary}\\r\\n`;\n const closeDelimiter = `\\r\\n--${boundary}--`;\n\n const metadata = buildMetadata({\n name: options.name,\n mimeType: options.mimeType,\n parentId: options.parentId,\n description: options.description,\n });\n\n const multipartRequestBody =\n delimiter +\n \"Content-Type: application/json\\r\\n\\r\\n\" +\n JSON.stringify(metadata) +\n delimiter +\n `Content-Type: ${options.mimeType}\\r\\n\\r\\n` +\n options.content +\n closeDelimiter;\n\n const response = await fetch(\n \"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name,mimeType,kind,createdTime,modifiedTime,size,webViewLink,webContentLink\",\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": `multipart/related; boundary=${boundary}`,\n },\n body: multipartRequestBody,\n },\n );\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Drive upload error: ${response.status} - ${error}`);\n }\n\n return response.json();\n },\n\n /**\n * Download file content as text\n */\n async downloadFile(fileId: string): Promise<string> {\n const accessToken = await getAccessToken();\n\n const response = await fetch(`${DRIVE_API_BASE}/files/${fileId}?alt=media`, {\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Drive download error: ${response.status} - ${error}`);\n }\n\n return response.text();\n },\n\n /**\n * Delete a file or folder (move to trash)\n */\n async deleteFile(fileId: string): Promise<void> {\n await driveApiRequest(`/files/${fileId}`, { method: \"DELETE\" });\n },\n\n /**\n * Copy a file\n */\n async copyFile(fileId: string, name: string, parentId?: string): Promise<DriveFile> {\n const metadata: Record<string, unknown> = { name };\n if (parentId) metadata.parents = [parentId];\n\n return driveApiRequest<DriveFile>(`/files/${fileId}/copy`, {\n method: \"POST\",\n body: JSON.stringify(metadata),\n });\n },\n\n /**\n * Update file metadata\n */\n async updateFile(\n fileId: string,\n updates: { name?: string; description?: string; starred?: boolean },\n ): Promise<DriveFile> {\n return driveApiRequest<DriveFile>(`/files/${fileId}`, {\n method: \"PATCH\",\n body: JSON.stringify(updates),\n });\n },\n };\n}\n\nexport type DriveClient = ReturnType<typeof createDriveClient>;\n",
|
|
613
|
+
"lib/oauth.ts": "import { type OAuthToken, tokenStore } from \"./token-store.ts\";\n\nexport interface OAuthProvider {\n name: string;\n authorizationUrl: string;\n tokenUrl: string;\n clientId: string;\n clientSecret: string;\n scopes: string[];\n callbackPath: string;\n}\n\nfunction buildTokenRequest(\n provider: OAuthProvider,\n body: Record<string, string>,\n): RequestInit {\n return {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n client_id: provider.clientId,\n client_secret: provider.clientSecret,\n ...body,\n }),\n };\n}\n\nasync function fetchToken(\n provider: OAuthProvider,\n body: Record<string, string>,\n errorPrefix: string,\n): Promise<any> {\n const response = await fetch(provider.tokenUrl, buildTokenRequest(provider, body));\n\n if (response.ok) {\n return response.json();\n }\n\n const error = await response.text();\n throw new Error(`${errorPrefix}: ${response.status} - ${error}`);\n}\n\nfunction toOAuthToken(data: any, fallbackRefreshToken?: string): OAuthToken {\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? fallbackRefreshToken,\n expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined,\n tokenType: data.token_type ?? \"Bearer\",\n scope: data.scope,\n };\n}\n\nexport function getAuthorizationUrl(\n provider: OAuthProvider,\n state: string,\n redirectUri: string,\n): string {\n const params = new URLSearchParams({\n client_id: provider.clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: provider.scopes.join(\" \"),\n state,\n access_type: \"offline\",\n prompt: \"consent\",\n });\n\n return `${provider.authorizationUrl}?${params.toString()}`;\n}\n\nexport async function exchangeCodeForTokens(\n provider: OAuthProvider,\n code: string,\n redirectUri: string,\n): Promise<OAuthToken> {\n const data = await fetchToken(\n provider,\n {\n code,\n grant_type: \"authorization_code\",\n redirect_uri: redirectUri,\n },\n \"Token exchange failed\",\n );\n\n return toOAuthToken(data);\n}\n\nexport async function refreshAccessToken(\n provider: OAuthProvider,\n refreshToken: string,\n): Promise<OAuthToken> {\n const data = await fetchToken(\n provider,\n {\n refresh_token: refreshToken,\n grant_type: \"refresh_token\",\n },\n \"Token refresh failed\",\n );\n\n return toOAuthToken(data, refreshToken);\n}\n\nexport async function getValidToken(\n provider: OAuthProvider,\n userId: string,\n service: string,\n): Promise<string | null> {\n const token = await tokenStore.getToken(userId, service);\n if (!token) return null;\n\n const isExpired = token.expiresAt\n ? token.expiresAt < Date.now() + 5 * 60 * 1000\n : false;\n\n if (!isExpired || !token.refreshToken) {\n return token.accessToken;\n }\n\n try {\n const newToken = await refreshAccessToken(provider, token.refreshToken);\n await tokenStore.setToken(userId, service, newToken);\n return newToken.accessToken;\n } catch {\n await tokenStore.revokeToken(userId, service);\n return null;\n }\n}\n",
|
|
614
|
+
"tools/create-folder.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createDriveClient } from \"../../lib/drive-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"create-folder\",\n description:\n \"Create a new folder in Google Drive. Can optionally specify a parent folder. Returns the new folder ID and details.\",\n inputSchema: z.object({\n name: z.string().describe(\"Name of the folder to create\"),\n parentId: z\n .string()\n .optional()\n .describe(\"ID of the parent folder. If not provided, creates in root.\"),\n description: z.string().optional().describe(\"Optional description for the folder\"),\n }),\n async execute({ name, parentId, description }) {\n const client = createDriveClient(DEFAULT_USER_ID);\n const folder = await client.createFolder({ name, parentId, description });\n\n return {\n id: folder.id,\n name: folder.name,\n mimeType: folder.mimeType,\n createdTime: folder.createdTime,\n modifiedTime: folder.modifiedTime,\n webViewLink: folder.webViewLink,\n parents: folder.parents,\n };\n },\n});\n",
|
|
615
|
+
"tools/upload-file.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createDriveClient } from \"../../lib/drive-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"upload-file\",\n description:\n \"Upload or create a text file in Google Drive. Supports plain text, JSON, CSV, Markdown, and other text formats. Returns the new file ID and details.\",\n inputSchema: z.object({\n name: z\n .string()\n .describe(\"Name of the file including extension (e.g., 'report.txt', 'data.json')\"),\n content: z.string().describe(\"Text content of the file\"),\n mimeType: z\n .string()\n .default(\"text/plain\")\n .describe(\n \"MIME type of the file. Examples: 'text/plain', 'application/json', 'text/csv', 'text/markdown'\",\n ),\n parentId: z\n .string()\n .optional()\n .describe(\"ID of the parent folder. If not provided, creates in root.\"),\n description: z.string().optional().describe(\"Optional description for the file\"),\n }),\n async execute({ name, content, mimeType, parentId, description }) {\n const client = createDriveClient(DEFAULT_USER_ID);\n const file = await client.uploadFile({ name, content, mimeType, parentId, description });\n\n return {\n id: file.id,\n name: file.name,\n mimeType: file.mimeType,\n size: file.size,\n createdTime: file.createdTime,\n modifiedTime: file.modifiedTime,\n webViewLink: file.webViewLink,\n webContentLink: file.webContentLink,\n };\n },\n});\n",
|
|
616
|
+
"tools/get-file.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createDriveClient } from \"../../lib/drive-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"get-file\",\n description:\n \"Get detailed metadata about a specific file or folder in Google Drive. Returns detailed information including sharing settings, owners, and capabilities.\",\n inputSchema: z.object({\n fileId: z.string().describe(\"The ID of the file or folder to retrieve\"),\n }),\n async execute({ fileId }) {\n const client = createDriveClient(DEFAULT_USER_ID);\n const file = await client.getFile(fileId);\n\n const lastModifyingUser = file.lastModifyingUser\n ? {\n name: file.lastModifyingUser.displayName,\n email: file.lastModifyingUser.emailAddress,\n photoLink: file.lastModifyingUser.photoLink,\n }\n : undefined;\n\n return {\n id: file.id,\n name: file.name,\n mimeType: file.mimeType,\n isFolder: file.mimeType === \"application/vnd.google-apps.folder\",\n size: file.size,\n createdTime: file.createdTime,\n modifiedTime: file.modifiedTime,\n webViewLink: file.webViewLink,\n webContentLink: file.webContentLink,\n iconLink: file.iconLink,\n thumbnailLink: file.thumbnailLink,\n parents: file.parents,\n starred: file.starred,\n trashed: file.trashed,\n shared: file.shared,\n owners: file.owners?.map((owner) => ({\n name: owner.displayName,\n email: owner.emailAddress,\n photoLink: owner.photoLink,\n })),\n lastModifyingUser,\n capabilities: file.capabilities,\n };\n },\n});\n",
|
|
617
|
+
"tools/search-files.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createDriveClient } from \"../../lib/drive-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"search-files\",\n description:\n \"Search for files and folders in Google Drive using queries. Supports searching by name, content, type, and more. Use Drive query syntax (e.g., \\\"name contains 'report'\\\", \\\"mimeType='application/pdf'\\\").\",\n inputSchema: z.object({\n query: z\n .string()\n .describe(\n \"Search query using Drive query syntax. Examples: \\\"name contains 'report'\\\", \\\"mimeType='application/pdf'\\\", \\\"fullText contains 'budget'\\\"\",\n ),\n pageSize: z\n .number()\n .min(1)\n .max(1000)\n .default(100)\n .describe(\"Maximum number of files to return\"),\n pageToken: z\n .string()\n .optional()\n .describe(\"Token for pagination to get next page of results\"),\n orderBy: z\n .enum([\n \"createdTime\",\n \"folder\",\n \"modifiedByMeTime\",\n \"modifiedTime\",\n \"name\",\n \"quotaBytesUsed\",\n \"recency\",\n \"sharedWithMeTime\",\n \"starred\",\n \"viewedByMeTime\",\n ])\n .optional()\n .describe(\"Field to sort results by\"),\n }),\n async execute({ query, pageSize, pageToken, orderBy }) {\n const client = createDriveClient(DEFAULT_USER_ID);\n\n const result = await client.searchFiles({\n query,\n pageSize,\n pageToken,\n orderBy: orderBy ? `${orderBy} desc` : undefined,\n });\n\n return {\n files: result.files.map((file) => ({\n id: file.id,\n name: file.name,\n mimeType: file.mimeType,\n isFolder: file.mimeType === \"application/vnd.google-apps.folder\",\n size: file.size,\n createdTime: file.createdTime,\n modifiedTime: file.modifiedTime,\n webViewLink: file.webViewLink,\n iconLink: file.iconLink,\n thumbnailLink: file.thumbnailLink,\n starred: file.starred,\n shared: file.shared,\n parents: file.parents,\n })),\n nextPageToken: result.nextPageToken,\n hasMore: Boolean(result.nextPageToken),\n incompleteSearch: result.incompleteSearch,\n };\n },\n});\n",
|
|
618
|
+
"tools/list-files.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createDriveClient } from \"../../lib/drive-client.ts\";\n\nconst DEFAULT_USER_ID = \"demo-user\";\n\nexport default tool({\n id: \"list-files\",\n description:\n \"List files and folders in Google Drive. Can list from a specific folder or root. Returns file names, IDs, types, and metadata.\",\n inputSchema: z.object({\n folderId: z\n .string()\n .optional()\n .describe(\n \"ID of the folder to list files from. If not provided, lists from root.\",\n ),\n pageSize: z\n .number()\n .min(1)\n .max(1000)\n .default(100)\n .describe(\"Maximum number of files to return\"),\n pageToken: z\n .string()\n .optional()\n .describe(\"Token for pagination to get next page of results\"),\n orderBy: z\n .enum([\n \"createdTime\",\n \"folder\",\n \"modifiedByMeTime\",\n \"modifiedTime\",\n \"name\",\n \"quotaBytesUsed\",\n \"recency\",\n \"sharedWithMeTime\",\n \"starred\",\n \"viewedByMeTime\",\n ])\n .optional()\n .describe(\"Field to sort results by\"),\n }),\n async execute({ folderId, pageSize, pageToken, orderBy }) {\n const client = createDriveClient(DEFAULT_USER_ID);\n\n const result = await client.listFiles({\n folderId,\n pageSize,\n pageToken,\n orderBy: orderBy ? `${orderBy} desc` : undefined,\n });\n\n return {\n files: result.files.map((file) => ({\n id: file.id,\n name: file.name,\n mimeType: file.mimeType,\n isFolder: file.mimeType === \"application/vnd.google-apps.folder\",\n size: file.size,\n createdTime: file.createdTime,\n modifiedTime: file.modifiedTime,\n webViewLink: file.webViewLink,\n iconLink: file.iconLink,\n thumbnailLink: file.thumbnailLink,\n starred: file.starred,\n shared: file.shared,\n })),\n nextPageToken: result.nextPageToken,\n hasMore: Boolean(result.nextPageToken),\n };\n },\n});\n",
|
|
619
|
+
"app/api/auth/drive/route.ts": "import { createOAuthInitHandler, driveConfig, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(driveConfig, { tokenStore: memoryTokenStore });\n",
|
|
620
|
+
"app/api/auth/drive/callback/route.ts": "/**\n * Google Drive OAuth Callback\n *\n * Handles the OAuth callback from Google and stores the tokens.\n */\n\nimport { createOAuthCallbackHandler, driveConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\n// Hybrid adapter: uses framework's memoryTokenStore for state (PKCE),\n// but user's tokenStore for actual token storage\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState(state: string) {\n return memoryTokenStore.getState(state);\n },\n setState(state: { state: string; codeVerifier?: string; createdAt: number }) {\n return memoryTokenStore.setState(state);\n },\n clearState(state: string) {\n return memoryTokenStore.clearState(state);\n },\n};\n\nexport const GET = createOAuthCallbackHandler(driveConfig, { tokenStore: hybridTokenStore });\n",
|
|
621
|
+
".env.example": "# Google Drive Integration\n# Create OAuth credentials at https://console.cloud.google.com/apis/credentials\n# Make sure to enable:\n# - Google Drive API: https://console.cloud.google.com/apis/library/drive.googleapis.com\n#\n# Note: These credentials are shared across all Google integrations (Gmail, Calendar, Sheets, Drive)\n\nGOOGLE_CLIENT_ID=your_client_id_here\nGOOGLE_CLIENT_SECRET=your_client_secret_here\n"
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
"integration:zendesk": {
|
|
625
|
+
"files": {
|
|
626
|
+
"lib/zendesk-client.ts": "import { getZendeskTokens } from \"./token-store.ts\";\n\nfunction getEnv(name: string): string | undefined {\n if (typeof Deno !== \"undefined\") {\n // @ts-ignore: Deno global\n return Deno.env.get(name);\n }\n // @ts-ignore: Node process\n return globalThis.process?.env?.[name];\n}\n\nexport interface ZendeskTicket {\n id: number;\n url: string;\n subject: string;\n description: string;\n status: \"new\" | \"open\" | \"pending\" | \"hold\" | \"solved\" | \"closed\";\n priority: \"urgent\" | \"high\" | \"normal\" | \"low\" | null;\n type: \"problem\" | \"incident\" | \"question\" | \"task\" | null;\n requester_id: number;\n submitter_id: number;\n assignee_id: number | null;\n tags: string[];\n created_at: string;\n updated_at: string;\n due_at: string | null;\n}\n\nexport interface ZendeskUser {\n id: number;\n url: string;\n name: string;\n email: string;\n role: \"end-user\" | \"agent\" | \"admin\";\n phone: string | null;\n photo: { url: string } | null;\n created_at: string;\n updated_at: string;\n}\n\nexport interface ZendeskComment {\n id: number;\n type: \"Comment\" | \"VoiceComment\";\n author_id: number;\n body: string;\n html_body: string;\n public: boolean;\n created_at: string;\n}\n\nexport interface ZendeskResponse<T> {\n [key: string]: T;\n}\n\nexport interface ZendeskListResponse<T> {\n [key: string]: T[];\n count?: number;\n next_page?: string | null;\n previous_page?: string | null;\n}\n\nexport class ZendeskClient {\n private subdomain: string;\n private accessToken: string | null = null;\n\n constructor() {\n const subdomain = getEnv(\"ZENDESK_SUBDOMAIN\");\n if (!subdomain) throw new Error(\"ZENDESK_SUBDOMAIN not configured\");\n this.subdomain = subdomain;\n }\n\n private get baseUrl(): string {\n return `https://${this.subdomain}.zendesk.com/api/v2`;\n }\n\n async ensureAuthenticated(): Promise<void> {\n const tokens = await getZendeskTokens();\n if (!tokens) {\n throw new Error(\n \"Zendesk not connected. Please connect via /api/auth/zendesk\",\n );\n }\n this.accessToken = tokens.accessToken;\n }\n\n private async request<T>(\n endpoint: string,\n options: RequestInit = {},\n ): Promise<T> {\n await this.ensureAuthenticated();\n\n const response = await fetch(`${this.baseUrl}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Zendesk API error: ${response.status} ${errorText}`);\n }\n\n return response.json();\n }\n\n async listTickets(options: {\n limit?: number;\n status?: string;\n priority?: string;\n assigneeId?: number;\n } = {}): Promise<ZendeskTicket[]> {\n const { limit, status, priority, assigneeId } = options;\n\n const queryParts: string[] = [];\n if (status) queryParts.push(`status:${status}`);\n if (priority) queryParts.push(`priority:${priority}`);\n if (assigneeId) queryParts.push(`assignee:${assigneeId}`);\n\n let endpoint = \"/tickets.json\";\n\n if (queryParts.length > 0) {\n endpoint = `/search.json?query=type:ticket ${queryParts.join(\" \")}`;\n if (limit) endpoint += `&per_page=${limit}`;\n } else if (limit) {\n endpoint += `?per_page=${limit}`;\n }\n\n const response = await this.request<ZendeskListResponse<ZendeskTicket>>(\n endpoint,\n );\n\n return response.tickets || response.results || [];\n }\n\n async getTicket(ticketId: number): Promise<ZendeskTicket> {\n const response = await this.request<ZendeskResponse<ZendeskTicket>>(\n `/tickets/${ticketId}.json`,\n );\n return response.ticket;\n }\n\n async createTicket(data: {\n subject: string;\n comment: { body: string };\n requester?: { name: string; email: string };\n priority?: \"urgent\" | \"high\" | \"normal\" | \"low\";\n type?: \"problem\" | \"incident\" | \"question\" | \"task\";\n tags?: string[];\n assignee_id?: number;\n }): Promise<ZendeskTicket> {\n const response = await this.request<ZendeskResponse<ZendeskTicket>>(\n \"/tickets.json\",\n {\n method: \"POST\",\n body: JSON.stringify({ ticket: data }),\n },\n );\n return response.ticket;\n }\n\n async updateTicket(\n ticketId: number,\n data: Partial<{\n subject: string;\n comment: { body: string; public: boolean };\n status: \"new\" | \"open\" | \"pending\" | \"hold\" | \"solved\" | \"closed\";\n priority: \"urgent\" | \"high\" | \"normal\" | \"low\";\n assignee_id: number;\n tags: string[];\n }>,\n ): Promise<ZendeskTicket> {\n const response = await this.request<ZendeskResponse<ZendeskTicket>>(\n `/tickets/${ticketId}.json`,\n {\n method: \"PUT\",\n body: JSON.stringify({ ticket: data }),\n },\n );\n return response.ticket;\n }\n\n async listUsers(options: {\n limit?: number;\n role?: string;\n } = {}): Promise<ZendeskUser[]> {\n const params = new URLSearchParams();\n if (options.limit) params.set(\"per_page\", String(options.limit));\n if (options.role) params.set(\"role\", options.role);\n\n const query = params.toString();\n const endpoint = `/users.json${query ? `?${query}` : \"\"}`;\n\n const response = await this.request<ZendeskListResponse<ZendeskUser>>(\n endpoint,\n );\n return response.users || [];\n }\n\n async getUser(userId: number): Promise<ZendeskUser> {\n const response = await this.request<ZendeskResponse<ZendeskUser>>(\n `/users/${userId}.json`,\n );\n return response.user;\n }\n\n async searchTickets(query: string, limit = 20): Promise<ZendeskTicket[]> {\n const params = new URLSearchParams({\n query: `type:ticket ${query}`,\n per_page: String(limit),\n });\n\n const response = await this.request<ZendeskListResponse<ZendeskTicket>>(\n `/search.json?${params}`,\n );\n return response.results || [];\n }\n\n addComment(\n ticketId: number,\n body: string,\n isPublic = true,\n ): Promise<ZendeskTicket> {\n return this.updateTicket(ticketId, { comment: { body, public: isPublic } });\n }\n}\n\nlet client: ZendeskClient | null = null;\n\nexport function getZendeskClient(): ZendeskClient {\n if (!client) client = new ZendeskClient();\n return client;\n}\n",
|
|
627
|
+
"tools/search-tickets.ts": "import { z } from \"zod\";\nimport { getZendeskClient } from \"../../lib/zendesk-client.ts\";\nimport { isZendeskConnected } from \"../../lib/token-store.ts\";\n\nexport default defineTool({\n id: \"zendesk-search-tickets\",\n description:\n \"Search tickets using Zendesk query syntax. Examples: 'status:open priority:urgent', 'subject:bug', 'tags:billing'\",\n inputSchema: z.object({\n query: z\n .string()\n .describe(\n \"Search query using Zendesk syntax (e.g., 'status:open priority:urgent', 'subject:refund', 'tags:billing')\",\n ),\n limit: z.number().optional().describe(\"Maximum number of results to return (default: 20)\"),\n }),\n async execute(input) {\n if (!(await isZendeskConnected())) {\n return {\n error: \"Zendesk not connected\",\n action: \"Please connect Zendesk via /api/auth/zendesk\",\n };\n }\n\n try {\n const client = getZendeskClient();\n const tickets = await client.searchTickets(input.query, input.limit);\n\n return {\n count: tickets.length,\n query: input.query,\n tickets: tickets.map(\n ({\n id,\n subject,\n description,\n status,\n priority,\n type,\n requester_id,\n assignee_id,\n tags,\n created_at,\n updated_at,\n }) => ({\n id,\n subject,\n description,\n status,\n priority,\n type,\n requester_id,\n assignee_id,\n tags,\n created_at,\n updated_at,\n }),\n ),\n };\n } catch (error) {\n return {\n error: error instanceof Error ? error.message : \"Failed to search tickets\",\n };\n }\n },\n});\n",
|
|
628
|
+
"tools/create-ticket.ts": "import { z } from \"zod\";\nimport { getZendeskClient } from \"../../lib/zendesk-client.ts\";\nimport { isZendeskConnected } from \"../../lib/token-store.ts\";\n\ntype TicketData = {\n subject: string;\n comment: { body: string };\n requester?: { name: string; email: string };\n priority?: \"urgent\" | \"high\" | \"normal\" | \"low\";\n type?: \"problem\" | \"incident\" | \"question\" | \"task\";\n tags?: string[];\n assignee_id?: number;\n};\n\nexport default defineTool({\n id: \"zendesk-create-ticket\",\n description: \"Create a new ticket in Zendesk\",\n inputSchema: z.object({\n subject: z.string().describe(\"Subject/title of the ticket\"),\n body: z.string().describe(\"Description/body content of the ticket\"),\n priority: z\n .enum([\"urgent\", \"high\", \"normal\", \"low\"])\n .optional()\n .describe(\"Priority level of the ticket\"),\n type: z\n .enum([\"problem\", \"incident\", \"question\", \"task\"])\n .optional()\n .describe(\"Type of ticket\"),\n tags: z.array(z.string()).optional().describe(\"Tags to add to the ticket\"),\n assigneeId: z.number().optional().describe(\"User ID to assign the ticket to\"),\n requesterName: z\n .string()\n .optional()\n .describe(\"Name of the requester (if creating on behalf)\"),\n requesterEmail: z\n .string()\n .optional()\n .describe(\"Email of the requester (if creating on behalf)\"),\n }),\n async execute(input) {\n if (!(await isZendeskConnected())) {\n return {\n error: \"Zendesk not connected\",\n action: \"Please connect Zendesk via /api/auth/zendesk\",\n };\n }\n\n try {\n const client = getZendeskClient();\n\n const ticketData: TicketData = {\n subject: input.subject,\n comment: { body: input.body },\n priority: input.priority,\n type: input.type,\n tags: input.tags,\n assignee_id: input.assigneeId,\n requester:\n input.requesterName && input.requesterEmail\n ? { name: input.requesterName, email: input.requesterEmail }\n : undefined,\n };\n\n const ticket = await client.createTicket(ticketData);\n\n return {\n success: true,\n id: ticket.id,\n url: ticket.url,\n subject: ticket.subject,\n status: ticket.status,\n priority: ticket.priority,\n type: ticket.type,\n message: `Ticket #${ticket.id} created successfully`,\n };\n } catch (error) {\n return {\n error: error instanceof Error ? error.message : \"Failed to create ticket\",\n };\n }\n },\n});\n",
|
|
629
|
+
"tools/get-ticket.ts": "import { z } from \"zod\";\nimport { getZendeskClient } from \"../../lib/zendesk-client.ts\";\nimport { isZendeskConnected } from \"../../lib/token-store.ts\";\n\nexport default defineTool({\n id: \"zendesk-get-ticket\",\n description: \"Get detailed information about a specific Zendesk ticket by ID\",\n inputSchema: z.object({\n ticketId: z.number().describe(\"The ticket ID to retrieve\"),\n }),\n async execute(input) {\n if (!(await isZendeskConnected())) {\n return {\n error: \"Zendesk not connected\",\n action: \"Please connect Zendesk via /api/auth/zendesk\",\n };\n }\n\n try {\n const client = getZendeskClient();\n const ticket = await client.getTicket(input.ticketId);\n\n return {\n ticket: {\n id: ticket.id,\n url: ticket.url,\n subject: ticket.subject,\n description: ticket.description,\n status: ticket.status,\n priority: ticket.priority,\n type: ticket.type,\n requester_id: ticket.requester_id,\n submitter_id: ticket.submitter_id,\n assignee_id: ticket.assignee_id,\n tags: ticket.tags,\n created_at: ticket.created_at,\n updated_at: ticket.updated_at,\n due_at: ticket.due_at,\n },\n };\n } catch (error) {\n return {\n error: error instanceof Error ? error.message : \"Failed to get ticket\",\n };\n }\n },\n});\n",
|
|
630
|
+
"tools/list-tickets.ts": "import { z } from \"zod\";\nimport { getZendeskClient } from \"../../lib/zendesk-client.ts\";\nimport { isZendeskConnected } from \"../../lib/token-store.ts\";\n\nexport default defineTool({\n id: \"zendesk-list-tickets\",\n description: \"List tickets from Zendesk with optional filters for status, priority, or assignee\",\n inputSchema: z.object({\n limit: z.number().optional().describe(\"Maximum number of tickets to return (default: 20)\"),\n status: z\n .enum([\"new\", \"open\", \"pending\", \"hold\", \"solved\", \"closed\"])\n .optional()\n .describe(\"Filter by ticket status\"),\n priority: z\n .enum([\"urgent\", \"high\", \"normal\", \"low\"])\n .optional()\n .describe(\"Filter by priority level\"),\n assigneeId: z.number().optional().describe(\"Filter by assignee user ID\"),\n }),\n async execute(input) {\n if (!(await isZendeskConnected())) {\n return {\n error: \"Zendesk not connected\",\n action: \"Please connect Zendesk via /api/auth/zendesk\",\n };\n }\n\n try {\n const client = getZendeskClient();\n const tickets = await client.listTickets({\n limit: input.limit,\n status: input.status,\n priority: input.priority,\n assigneeId: input.assigneeId,\n });\n\n return {\n count: tickets.length,\n tickets: tickets.map(\n ({\n id,\n subject,\n status,\n priority,\n type,\n requester_id,\n assignee_id,\n tags,\n created_at,\n updated_at,\n }) => ({\n id,\n subject,\n status,\n priority,\n type,\n requester_id,\n assignee_id,\n tags,\n created_at,\n updated_at,\n }),\n ),\n };\n } catch (error) {\n return {\n error: error instanceof Error ? error.message : \"Failed to list tickets\",\n };\n }\n },\n});\n",
|
|
631
|
+
"app/api/auth/zendesk/route.ts": "const getEnv = (name: string): string | undefined => {\n // @ts-ignore: Deno global\n if (typeof Deno !== \"undefined\") return Deno.env.get(name);\n // @ts-ignore: Node process\n return globalThis.process?.env?.[name];\n};\n\nexport function GET(request: Request): Response {\n const subdomain = getEnv(\"ZENDESK_SUBDOMAIN\");\n const clientId = getEnv(\"ZENDESK_CLIENT_ID\");\n\n if (!subdomain || !clientId) {\n return Response.json({ error: \"Zendesk OAuth not configured\" }, { status: 500 });\n }\n\n const { protocol, host } = new URL(request.url);\n const baseUrl = getEnv(\"NEXT_PUBLIC_APP_URL\") ?? `${protocol}//${host}`;\n const redirectUri = `${baseUrl}/api/auth/zendesk/callback`;\n\n const authUrl = new URL(`https://${subdomain}.zendesk.com/oauth/authorizations/new`);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUri);\n authUrl.searchParams.set(\"scope\", \"read write\");\n authUrl.searchParams.set(\"state\", crypto.randomUUID());\n\n return Response.redirect(authUrl.toString(), 302);\n}\n",
|
|
632
|
+
"app/api/auth/zendesk/callback/route.ts": "import { setZendeskTokens } from \"../../../../../lib/token-store.ts\";\n\nfunction getEnv(name: string): string | undefined {\n if (typeof Deno !== \"undefined\") {\n // @ts-ignore: Deno global\n return Deno.env.get(name);\n }\n // @ts-ignore: Node process\n return globalThis.process?.env?.[name];\n}\n\nexport async function GET(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const code = url.searchParams.get(\"code\");\n const error = url.searchParams.get(\"error\");\n const errorDescription = url.searchParams.get(\"error_description\");\n\n const baseUrl = getEnv(\"NEXT_PUBLIC_APP_URL\") ?? `${url.protocol}//${url.host}`;\n\n if (error) {\n console.error(\"Zendesk OAuth error:\", error, errorDescription);\n const description = encodeURIComponent(errorDescription ?? error);\n return Response.redirect(\n `${baseUrl}/?error=zendesk_oauth_failed&description=${description}`,\n 302,\n );\n }\n\n if (!code) return Response.redirect(`${baseUrl}/?error=no_code`, 302);\n\n const subdomain = getEnv(\"ZENDESK_SUBDOMAIN\");\n const clientId = getEnv(\"ZENDESK_CLIENT_ID\");\n const clientSecret = getEnv(\"ZENDESK_CLIENT_SECRET\");\n\n if (!subdomain || !clientId || !clientSecret) {\n return Response.redirect(`${baseUrl}/?error=zendesk_not_configured`, 302);\n }\n\n const redirectUri = `${baseUrl}/api/auth/zendesk/callback`;\n\n try {\n const tokenResponse = await fetch(\n `https://${subdomain}.zendesk.com/oauth/tokens`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n grant_type: \"authorization_code\",\n code,\n client_id: clientId,\n client_secret: clientSecret,\n redirect_uri: redirectUri,\n scope: \"read write\",\n }),\n },\n );\n\n if (!tokenResponse.ok) {\n const errorText = await tokenResponse.text();\n console.error(\"Zendesk token exchange failed:\", errorText);\n return Response.redirect(`${baseUrl}/?error=token_exchange_failed`, 302);\n }\n\n const tokens = await tokenResponse.json();\n\n await setZendeskTokens({\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: tokens.expires_in\n ? Date.now() + tokens.expires_in * 1000\n : undefined,\n subdomain,\n });\n\n return Response.redirect(`${baseUrl}/?connected=zendesk`, 302);\n } catch (error) {\n console.error(\"Zendesk OAuth error:\", err);\n return Response.redirect(`${baseUrl}/?error=zendesk_oauth_failed`, 302);\n }\n}\n",
|
|
633
|
+
".env.example": "# Zendesk OAuth Configuration\n# Get these from your Zendesk Admin Center: Apps and integrations > APIs > Zendesk API > OAuth Clients\nZENDESK_SUBDOMAIN=your-subdomain\nZENDESK_CLIENT_ID=your_client_id\nZENDESK_CLIENT_SECRET=your_client_secret\n"
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
"integration:salesforce": {
|
|
637
|
+
"files": {
|
|
638
|
+
"lib/salesforce-client.ts": "import { getAccessToken, getInstanceUrl } from \"./token-store.ts\";\n\nconst API_VERSION = \"v59.0\";\n\ninterface SalesforceQueryResponse<T> {\n totalSize: number;\n done: boolean;\n records: T[];\n nextRecordsUrl?: string;\n}\n\ninterface SalesforceAccount {\n Id: string;\n Name: string;\n Type?: string;\n Industry?: string;\n Website?: string;\n Phone?: string;\n BillingStreet?: string;\n BillingCity?: string;\n BillingState?: string;\n BillingPostalCode?: string;\n BillingCountry?: string;\n NumberOfEmployees?: number;\n AnnualRevenue?: number;\n Description?: string;\n CreatedDate: string;\n LastModifiedDate: string;\n [key: string]: any;\n}\n\ninterface SalesforceContact {\n Id: string;\n FirstName?: string;\n LastName: string;\n Email?: string;\n Phone?: string;\n MobilePhone?: string;\n Title?: string;\n Department?: string;\n AccountId?: string;\n MailingStreet?: string;\n MailingCity?: string;\n MailingState?: string;\n MailingPostalCode?: string;\n MailingCountry?: string;\n Description?: string;\n CreatedDate: string;\n LastModifiedDate: string;\n [key: string]: any;\n}\n\ninterface SalesforceOpportunity {\n Id: string;\n Name: string;\n AccountId?: string;\n Amount?: number;\n StageName: string;\n Probability?: number;\n CloseDate: string;\n Type?: string;\n LeadSource?: string;\n Description?: string;\n NextStep?: string;\n IsClosed: boolean;\n IsWon: boolean;\n ForecastCategory?: string;\n CreatedDate: string;\n LastModifiedDate: string;\n [key: string]: any;\n}\n\ninterface SalesforceLead {\n Id: string;\n FirstName?: string;\n LastName: string;\n Company: string;\n Email?: string;\n Phone?: string;\n MobilePhone?: string;\n Title?: string;\n Status: string;\n LeadSource?: string;\n Industry?: string;\n Street?: string;\n City?: string;\n State?: string;\n PostalCode?: string;\n Country?: string;\n Website?: string;\n Description?: string;\n Rating?: string;\n CreatedDate: string;\n LastModifiedDate: string;\n [key: string]: any;\n}\n\nasync function salesforceFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Salesforce. Please connect your account.\");\n }\n\n const instanceUrl = getInstanceUrl();\n if (!instanceUrl) {\n throw new Error(\"Salesforce instance URL not found. Please reconnect your account.\");\n }\n\n const url = endpoint.startsWith(\"http\")\n ? endpoint\n : `${instanceUrl}/services/data/${API_VERSION}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as any));\n const message = error?.[0]?.message ?? error?.message ?? response.statusText;\n throw new Error(`Salesforce API error: ${response.status} ${message}`);\n }\n\n return response.json();\n}\n\nexport function query<T = any>(soql: string): Promise<SalesforceQueryResponse<T>> {\n return salesforceFetch<SalesforceQueryResponse<T>>(`/query?q=${encodeURIComponent(soql)}`);\n}\n\nfunction buildListSoql(params: {\n object: string;\n fields: string[];\n where?: string;\n limit: number;\n offset: number;\n}): string {\n const { object, fields, where, limit, offset } = params;\n let soql = `SELECT ${fields.join(\", \")} FROM ${object}`;\n if (where) soql += ` WHERE ${where}`;\n soql += ` ORDER BY LastModifiedDate DESC LIMIT ${limit} OFFSET ${offset}`;\n return soql;\n}\n\nasync function getSingleRecord<T>(params: {\n object: string;\n id: string;\n fields: string[];\n notFoundMessage: string;\n}): Promise<T> {\n const { object, id, fields, notFoundMessage } = params;\n const soql = `SELECT ${fields.join(\", \")} FROM ${object} WHERE Id = '${id}'`;\n const result = await query<T>(soql);\n\n if (result.totalSize === 0) throw new Error(notFoundMessage);\n return result.records[0];\n}\n\n// ============================================================================\n// ACCOUNTS\n// ============================================================================\n\nexport function listAccounts(options?: {\n limit?: number;\n offset?: number;\n fields?: string[];\n}): Promise<SalesforceQueryResponse<SalesforceAccount>> {\n const limit = options?.limit ?? 10;\n const offset = options?.offset ?? 0;\n const fields = options?.fields ?? [\n \"Id\",\n \"Name\",\n \"Type\",\n \"Industry\",\n \"Website\",\n \"Phone\",\n \"BillingCity\",\n \"BillingState\",\n \"BillingCountry\",\n \"NumberOfEmployees\",\n \"AnnualRevenue\",\n \"CreatedDate\",\n \"LastModifiedDate\",\n ];\n\n return query<SalesforceAccount>(\n buildListSoql({ object: \"Account\", fields, limit, offset }),\n );\n}\n\nexport function getAccount(accountId: string, fields?: string[]): Promise<SalesforceAccount> {\n const selectedFields = fields ?? [\n \"Id\",\n \"Name\",\n \"Type\",\n \"Industry\",\n \"Website\",\n \"Phone\",\n \"BillingStreet\",\n \"BillingCity\",\n \"BillingState\",\n \"BillingPostalCode\",\n \"BillingCountry\",\n \"NumberOfEmployees\",\n \"AnnualRevenue\",\n \"Description\",\n \"CreatedDate\",\n \"LastModifiedDate\",\n ];\n\n return getSingleRecord<SalesforceAccount>({\n object: \"Account\",\n id: accountId,\n fields: selectedFields,\n notFoundMessage: `Account with ID ${accountId} not found`,\n });\n}\n\nexport function createAccount(data: {\n Name: string;\n Type?: string;\n Industry?: string;\n Website?: string;\n Phone?: string;\n BillingStreet?: string;\n BillingCity?: string;\n BillingState?: string;\n BillingPostalCode?: string;\n BillingCountry?: string;\n NumberOfEmployees?: number;\n AnnualRevenue?: number;\n Description?: string;\n [key: string]: any;\n}): Promise<{ id: string; success: boolean; errors: any[] }> {\n return salesforceFetch(\"/sobjects/Account\", {\n method: \"POST\",\n body: JSON.stringify(data),\n });\n}\n\n// ============================================================================\n// CONTACTS\n// ============================================================================\n\nexport function listContacts(options?: {\n limit?: number;\n offset?: number;\n fields?: string[];\n accountId?: string;\n}): Promise<SalesforceQueryResponse<SalesforceContact>> {\n const limit = options?.limit ?? 10;\n const offset = options?.offset ?? 0;\n const fields = options?.fields ?? [\n \"Id\",\n \"FirstName\",\n \"LastName\",\n \"Email\",\n \"Phone\",\n \"Title\",\n \"Department\",\n \"AccountId\",\n \"MailingCity\",\n \"MailingState\",\n \"MailingCountry\",\n \"CreatedDate\",\n \"LastModifiedDate\",\n ];\n\n const where = options?.accountId ? `AccountId = '${options.accountId}'` : undefined;\n\n return query<SalesforceContact>(\n buildListSoql({ object: \"Contact\", fields, where, limit, offset }),\n );\n}\n\nexport function getContact(contactId: string, fields?: string[]): Promise<SalesforceContact> {\n const selectedFields = fields ?? [\n \"Id\",\n \"FirstName\",\n \"LastName\",\n \"Email\",\n \"Phone\",\n \"MobilePhone\",\n \"Title\",\n \"Department\",\n \"AccountId\",\n \"MailingStreet\",\n \"MailingCity\",\n \"MailingState\",\n \"MailingPostalCode\",\n \"MailingCountry\",\n \"Description\",\n \"CreatedDate\",\n \"LastModifiedDate\",\n ];\n\n return getSingleRecord<SalesforceContact>({\n object: \"Contact\",\n id: contactId,\n fields: selectedFields,\n notFoundMessage: `Contact with ID ${contactId} not found`,\n });\n}\n\nexport function createContact(data: {\n LastName: string;\n FirstName?: string;\n Email?: string;\n Phone?: string;\n MobilePhone?: string;\n Title?: string;\n Department?: string;\n AccountId?: string;\n MailingStreet?: string;\n MailingCity?: string;\n MailingState?: string;\n MailingPostalCode?: string;\n MailingCountry?: string;\n Description?: string;\n [key: string]: any;\n}): Promise<{ id: string; success: boolean; errors: any[] }> {\n return salesforceFetch(\"/sobjects/Contact\", {\n method: \"POST\",\n body: JSON.stringify(data),\n });\n}\n\n// ============================================================================\n// OPPORTUNITIES\n// ============================================================================\n\nexport function listOpportunities(options?: {\n limit?: number;\n offset?: number;\n fields?: string[];\n accountId?: string;\n}): Promise<SalesforceQueryResponse<SalesforceOpportunity>> {\n const limit = options?.limit ?? 10;\n const offset = options?.offset ?? 0;\n const fields = options?.fields ?? [\n \"Id\",\n \"Name\",\n \"AccountId\",\n \"Amount\",\n \"StageName\",\n \"Probability\",\n \"CloseDate\",\n \"Type\",\n \"LeadSource\",\n \"IsClosed\",\n \"IsWon\",\n \"ForecastCategory\",\n \"CreatedDate\",\n \"LastModifiedDate\",\n ];\n\n const where = options?.accountId ? `AccountId = '${options.accountId}'` : undefined;\n\n return query<SalesforceOpportunity>(\n buildListSoql({ object: \"Opportunity\", fields, where, limit, offset }),\n );\n}\n\nexport function getOpportunity(\n opportunityId: string,\n fields?: string[],\n): Promise<SalesforceOpportunity> {\n const selectedFields = fields ?? [\n \"Id\",\n \"Name\",\n \"AccountId\",\n \"Amount\",\n \"StageName\",\n \"Probability\",\n \"CloseDate\",\n \"Type\",\n \"LeadSource\",\n \"Description\",\n \"NextStep\",\n \"IsClosed\",\n \"IsWon\",\n \"ForecastCategory\",\n \"CreatedDate\",\n \"LastModifiedDate\",\n ];\n\n return getSingleRecord<SalesforceOpportunity>({\n object: \"Opportunity\",\n id: opportunityId,\n fields: selectedFields,\n notFoundMessage: `Opportunity with ID ${opportunityId} not found`,\n });\n}\n\nexport function createOpportunity(data: {\n Name: string;\n StageName: string;\n CloseDate: string;\n AccountId?: string;\n Amount?: number;\n Probability?: number;\n Type?: string;\n LeadSource?: string;\n Description?: string;\n NextStep?: string;\n [key: string]: any;\n}): Promise<{ id: string; success: boolean; errors: any[] }> {\n return salesforceFetch(\"/sobjects/Opportunity\", {\n method: \"POST\",\n body: JSON.stringify(data),\n });\n}\n\n// ============================================================================\n// LEADS\n// ============================================================================\n\nexport function listLeads(options?: {\n limit?: number;\n offset?: number;\n fields?: string[];\n status?: string;\n}): Promise<SalesforceQueryResponse<SalesforceLead>> {\n const limit = options?.limit ?? 10;\n const offset = options?.offset ?? 0;\n const fields = options?.fields ?? [\n \"Id\",\n \"FirstName\",\n \"LastName\",\n \"Company\",\n \"Email\",\n \"Phone\",\n \"Title\",\n \"Status\",\n \"LeadSource\",\n \"Industry\",\n \"City\",\n \"State\",\n \"Country\",\n \"Rating\",\n \"CreatedDate\",\n \"LastModifiedDate\",\n ];\n\n const where = options?.status ? `Status = '${options.status}'` : undefined;\n\n return query<SalesforceLead>(buildListSoql({ object: \"Lead\", fields, where, limit, offset }));\n}\n\nexport function createLead(data: {\n LastName: string;\n Company: string;\n FirstName?: string;\n Email?: string;\n Phone?: string;\n MobilePhone?: string;\n Title?: string;\n Status?: string;\n LeadSource?: string;\n Industry?: string;\n Street?: string;\n City?: string;\n State?: string;\n PostalCode?: string;\n Country?: string;\n Website?: string;\n Description?: string;\n Rating?: string;\n [key: string]: any;\n}): Promise<{ id: string; success: boolean; errors: any[] }> {\n const leadData = { ...data, Status: data.Status ?? \"Open - Not Contacted\" };\n\n return salesforceFetch(\"/sobjects/Lead\", {\n method: \"POST\",\n body: JSON.stringify(leadData),\n });\n}\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\nexport function formatContactName(contact: SalesforceContact): string {\n const parts = [contact.FirstName, contact.LastName].filter(Boolean);\n return parts.length ? parts.join(\" \") : contact.Email ?? \"Unnamed Contact\";\n}\n\nexport function formatLeadName(lead: SalesforceLead): string {\n const parts = [lead.FirstName, lead.LastName].filter(Boolean);\n return parts.length ? parts.join(\" \") : lead.Email ?? \"Unnamed Lead\";\n}\n\nexport function formatAddress(\n street?: string,\n city?: string,\n state?: string,\n postalCode?: string,\n country?: string,\n): string {\n return [street, city, state, postalCode, country].filter(Boolean).join(\", \");\n}\n\nexport type {\n SalesforceAccount,\n SalesforceContact,\n SalesforceLead,\n SalesforceOpportunity,\n SalesforceQueryResponse,\n};\n",
|
|
639
|
+
"tools/list-accounts.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listAccounts } from \"../../lib/salesforce-client.ts\";\n\nexport default tool({\n id: \"list-accounts\",\n description:\n \"List accounts from your Salesforce CRM. Returns account information including name, type, industry, website, and billing details.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(10)\n .describe(\"Maximum number of accounts to return\"),\n offset: z\n .number()\n .min(0)\n .default(0)\n .describe(\"Number of records to skip for pagination\"),\n fields: z\n .array(z.string())\n .optional()\n .describe(\"Additional fields to retrieve (e.g., Description, Owner.Name, ParentId)\"),\n }),\n async execute({ limit, offset, fields }) {\n const response = await listAccounts({ limit, offset, fields });\n\n return {\n accounts: response.records.map((account) => {\n const additionalFields = fields\n ? Object.fromEntries(\n fields\n .filter((field) => account[field] !== undefined)\n .map((field) => [field, account[field]]),\n )\n : undefined;\n\n return {\n id: account.Id,\n name: account.Name,\n type: account.Type,\n industry: account.Industry,\n website: account.Website,\n phone: account.Phone,\n billingCity: account.BillingCity,\n billingState: account.BillingState,\n billingCountry: account.BillingCountry,\n numberOfEmployees: account.NumberOfEmployees,\n annualRevenue: account.AnnualRevenue,\n createdDate: account.CreatedDate,\n lastModifiedDate: account.LastModifiedDate,\n additionalFields,\n };\n }),\n totalSize: response.totalSize,\n hasMore: !response.done,\n };\n },\n});\n",
|
|
640
|
+
"tools/create-lead.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createLead, formatLeadName } from \"../../lib/salesforce-client.ts\";\n\nexport default tool({\n id: \"create-lead\",\n description:\n \"Create a new lead in Salesforce CRM. LastName and Company are required, other fields are optional.\",\n inputSchema: z.object({\n lastName: z.string().describe(\"Last name (required)\"),\n company: z.string().describe(\"Company name (required)\"),\n firstName: z.string().optional().describe(\"First name\"),\n email: z.string().email().optional().describe(\"Email address\"),\n phone: z.string().optional().describe(\"Phone number\"),\n mobilePhone: z.string().optional().describe(\"Mobile phone number\"),\n title: z.string().optional().describe(\"Job title\"),\n status: z\n .string()\n .optional()\n .describe(\n 'Lead status (e.g., \"Open - Not Contacted\", \"Working - Contacted\", \"Closed - Converted\")',\n ),\n leadSource: z\n .string()\n .optional()\n .describe('Lead source (e.g., \"Web\", \"Phone Inquiry\", \"Partner Referral\")'),\n industry: z.string().optional().describe(\"Industry\"),\n street: z.string().optional().describe(\"Street address\"),\n city: z.string().optional().describe(\"City\"),\n state: z.string().optional().describe(\"State/Province\"),\n postalCode: z.string().optional().describe(\"Postal code\"),\n country: z.string().optional().describe(\"Country\"),\n website: z.string().optional().describe(\"Website URL\"),\n description: z.string().optional().describe(\"Description or notes about the lead\"),\n rating: z.string().optional().describe('Lead rating (e.g., \"Hot\", \"Warm\", \"Cold\")'),\n }),\n async execute(input): Promise<{\n id: string;\n name: string;\n lastName: string;\n firstName?: string;\n company: string;\n email?: string;\n phone?: string;\n title?: string;\n status: string;\n message: string;\n }> {\n const leadData: Record<string, any> = {\n LastName: input.lastName,\n Company: input.company,\n };\n\n const optionalFields: Array<[keyof typeof input, string]> = [\n [\"firstName\", \"FirstName\"],\n [\"email\", \"Email\"],\n [\"phone\", \"Phone\"],\n [\"mobilePhone\", \"MobilePhone\"],\n [\"title\", \"Title\"],\n [\"status\", \"Status\"],\n [\"leadSource\", \"LeadSource\"],\n [\"industry\", \"Industry\"],\n [\"street\", \"Street\"],\n [\"city\", \"City\"],\n [\"state\", \"State\"],\n [\"postalCode\", \"PostalCode\"],\n [\"country\", \"Country\"],\n [\"website\", \"Website\"],\n [\"description\", \"Description\"],\n [\"rating\", \"Rating\"],\n ];\n\n for (const [inputKey, sfKey] of optionalFields) {\n const value = input[inputKey];\n if (value) leadData[sfKey] = value;\n }\n\n const result = await createLead(leadData);\n\n if (!result.success) {\n throw new Error(`Failed to create lead: ${JSON.stringify(result.errors)}`);\n }\n\n const lead = {\n FirstName: input.firstName,\n LastName: input.lastName,\n Email: input.email,\n };\n\n const name = formatLeadName(lead);\n\n return {\n id: result.id,\n name,\n lastName: input.lastName,\n firstName: input.firstName,\n company: input.company,\n email: input.email,\n phone: input.phone,\n title: input.title,\n status: input.status || \"Open - Not Contacted\",\n message: `Successfully created lead: ${name} at ${input.company}`,\n };\n },\n});\n",
|
|
641
|
+
"tools/get-account.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatAddress, getAccount } from \"../../lib/salesforce-client.ts\";\n\nexport default tool({\n id: \"get-account\",\n description:\n \"Get detailed information about a specific account in Salesforce CRM by their account ID.\",\n inputSchema: z.object({\n accountId: z\n .string()\n .describe(\"The Salesforce account ID (e.g., 001XXXXXXXXXXXXXXX)\"),\n fields: z\n .array(z.string())\n .optional()\n .describe(\n \"Additional fields to retrieve (e.g., Description, Owner.Name, ParentId)\",\n ),\n }),\n async execute({ accountId, fields }) {\n const account = await getAccount(accountId, fields);\n\n const billingAddress =\n formatAddress(\n account.BillingStreet,\n account.BillingCity,\n account.BillingState,\n account.BillingPostalCode,\n account.BillingCountry,\n ) || undefined;\n\n const additionalFields = fields\n ? Object.fromEntries(\n fields\n .filter((field) => account[field] !== undefined)\n .map((field) => [field, account[field]]),\n )\n : undefined;\n\n return {\n id: account.Id,\n name: account.Name,\n type: account.Type,\n industry: account.Industry,\n website: account.Website,\n phone: account.Phone,\n billingAddress,\n billingStreet: account.BillingStreet,\n billingCity: account.BillingCity,\n billingState: account.BillingState,\n billingPostalCode: account.BillingPostalCode,\n billingCountry: account.BillingCountry,\n numberOfEmployees: account.NumberOfEmployees,\n annualRevenue: account.AnnualRevenue,\n description: account.Description,\n createdDate: account.CreatedDate,\n lastModifiedDate: account.LastModifiedDate,\n additionalFields,\n };\n },\n});\n",
|
|
642
|
+
"tools/list-opportunities.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listOpportunities } from \"../../lib/salesforce-client.ts\";\n\nexport default tool({\n id: \"list-opportunities\",\n description:\n \"List sales opportunities from your Salesforce CRM. Returns opportunity information including name, amount, stage, close date, and account association.\",\n inputSchema: z.object({\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(10)\n .describe(\"Maximum number of opportunities to return\"),\n offset: z\n .number()\n .min(0)\n .default(0)\n .describe(\"Number of records to skip for pagination\"),\n accountId: z.string().optional().describe(\"Filter opportunities by Account ID\"),\n fields: z\n .array(z.string())\n .optional()\n .describe(\"Additional fields to retrieve (e.g., Account.Name, Owner.Name, Description)\"),\n }),\n async execute({ limit, offset, accountId, fields }) {\n const response = await listOpportunities({ limit, offset, accountId, fields });\n\n return {\n opportunities: response.records.map((opportunity) => {\n const additionalFields = fields\n ? Object.fromEntries(\n fields\n .filter((field) => opportunity[field] !== undefined)\n .map((field) => [field, opportunity[field]]),\n )\n : undefined;\n\n return {\n id: opportunity.Id,\n name: opportunity.Name,\n accountId: opportunity.AccountId,\n amount: opportunity.Amount,\n stageName: opportunity.StageName,\n probability: opportunity.Probability,\n closeDate: opportunity.CloseDate,\n type: opportunity.Type,\n leadSource: opportunity.LeadSource,\n isClosed: opportunity.IsClosed,\n isWon: opportunity.IsWon,\n forecastCategory: opportunity.ForecastCategory,\n createdDate: opportunity.CreatedDate,\n lastModifiedDate: opportunity.LastModifiedDate,\n additionalFields,\n };\n }),\n totalSize: response.totalSize,\n hasMore: !response.done,\n };\n },\n});\n",
|
|
643
|
+
"tools/list-contacts.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatContactName, listContacts } from \"../../lib/salesforce-client.ts\";\n\nexport default tool({\n id: \"list-contacts\",\n description:\n \"List contacts from your Salesforce CRM. Returns contact information including name, email, phone, title, and account association.\",\n inputSchema: z.object({\n limit: z.number().min(1).max(100).default(10).describe(\"Maximum number of contacts to return\"),\n offset: z.number().min(0).default(0).describe(\"Number of records to skip for pagination\"),\n accountId: z.string().optional().describe(\"Filter contacts by Account ID\"),\n fields: z\n .array(z.string())\n .optional()\n .describe(\"Additional fields to retrieve (e.g., Account.Name, Owner.Name, LeadSource)\"),\n }),\n async execute({ limit, offset, accountId, fields }) {\n const response = await listContacts({ limit, offset, accountId, fields });\n\n return {\n contacts: response.records.map((contact) => {\n const additionalFields = fields\n ? Object.fromEntries(\n fields\n .filter((field) => contact[field] !== undefined)\n .map((field) => [field, contact[field]]),\n )\n : undefined;\n\n return {\n id: contact.Id,\n name: formatContactName(contact),\n firstName: contact.FirstName,\n lastName: contact.LastName,\n email: contact.Email,\n phone: contact.Phone,\n mobilePhone: contact.MobilePhone,\n title: contact.Title,\n department: contact.Department,\n accountId: contact.AccountId,\n mailingCity: contact.MailingCity,\n mailingState: contact.MailingState,\n mailingCountry: contact.MailingCountry,\n createdDate: contact.CreatedDate,\n lastModifiedDate: contact.LastModifiedDate,\n additionalFields,\n };\n }),\n totalSize: response.totalSize,\n hasMore: !response.done,\n };\n },\n});\n",
|
|
644
|
+
"app/api/auth/salesforce/route.ts": "import { createOAuthInitHandler, memoryTokenStore, salesforceConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(salesforceConfig, { tokenStore: memoryTokenStore });\n",
|
|
645
|
+
"app/api/auth/salesforce/callback/route.ts": "/**\n * Salesforce OAuth Callback\n *\n * Handles the OAuth callback from Salesforce and stores the tokens.\n */\n\nimport { createOAuthCallbackHandler, memoryTokenStore, salesforceConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\n// Hybrid adapter: uses framework's memoryTokenStore for state (PKCE),\n// but user's tokenStore for actual token storage\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(salesforceConfig, { tokenStore: hybridTokenStore });\n"
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
"integration:mailchimp": {
|
|
649
|
+
"files": {
|
|
650
|
+
"lib/mailchimp-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nlet MAILCHIMP_BASE_URL = \"https://us1.api.mailchimp.com/3.0\";\n\ninterface MailchimpCampaign {\n id: string;\n web_id: number;\n type: string;\n create_time: string;\n archive_url: string;\n long_archive_url: string;\n status: string;\n emails_sent: number;\n send_time?: string;\n content_type: string;\n needs_block_refresh: boolean;\n recipients: {\n list_id: string;\n list_name: string;\n segment_text?: string;\n };\n settings: {\n subject_line: string;\n preview_text?: string;\n title: string;\n from_name: string;\n reply_to: string;\n };\n tracking: {\n opens: boolean;\n html_clicks: boolean;\n text_clicks: boolean;\n };\n report_summary?: {\n opens: number;\n unique_opens: number;\n open_rate: number;\n clicks: number;\n subscriber_clicks: number;\n click_rate: number;\n };\n}\n\ninterface MailchimpList {\n id: string;\n web_id: number;\n name: string;\n contact: {\n company: string;\n address1: string;\n city: string;\n state: string;\n zip: string;\n country: string;\n };\n permission_reminder: string;\n campaign_defaults: {\n from_name: string;\n from_email: string;\n subject: string;\n language: string;\n };\n stats: {\n member_count: number;\n total_contacts: number;\n unsubscribe_count: number;\n cleaned_count: number;\n member_count_since_send: number;\n unsubscribe_count_since_send: number;\n cleaned_count_since_send: number;\n campaign_count: number;\n open_rate: number;\n click_rate: number;\n };\n date_created: string;\n list_rating: number;\n subscribe_url_short: string;\n subscribe_url_long: string;\n}\n\ninterface MailchimpMember {\n id: string;\n email_address: string;\n unique_email_id: string;\n contact_id: string;\n full_name: string;\n web_id: number;\n email_type: string;\n status: \"subscribed\" | \"unsubscribed\" | \"cleaned\" | \"pending\" | \"transactional\";\n merge_fields: Record<string, unknown>;\n stats: {\n avg_open_rate: number;\n avg_click_rate: number;\n };\n ip_signup?: string;\n timestamp_signup?: string;\n ip_opt?: string;\n timestamp_opt?: string;\n member_rating: number;\n last_changed: string;\n language: string;\n vip: boolean;\n email_client?: string;\n location?: {\n latitude: number;\n longitude: number;\n gmtoff: number;\n dstoff: number;\n country_code: string;\n timezone: string;\n };\n tags: Array<{ id: number; name: string }>;\n}\n\ninterface MailchimpMetadata {\n dc: string;\n role: string;\n accountname: string;\n user_id: string;\n login: {\n email: string;\n avatar?: string;\n login_id: string;\n login_name: string;\n login_email: string;\n };\n}\n\nfunction buildQueryString(params: Record<string, string | number | undefined>): string {\n const searchParams = new URLSearchParams();\n\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined) continue;\n searchParams.set(key, String(value));\n }\n\n const query = searchParams.toString();\n return query ? `?${query}` : \"\";\n}\n\nasync function fetchMetadata(token: string): Promise<MailchimpMetadata> {\n const response = await fetch(\"https://login.mailchimp.com/oauth2/metadata\", {\n headers: { Authorization: `OAuth ${token}` },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch Mailchimp metadata: ${response.statusText}`);\n }\n\n return response.json();\n}\n\nasync function initializeBaseUrl(): Promise<void> {\n const token = await getAccessToken();\n if (!token) return;\n\n try {\n const metadata = await fetchMetadata(token);\n MAILCHIMP_BASE_URL = `https://${metadata.dc}.api.mailchimp.com/3.0`;\n } catch (error) {\n // Fallback to us1 if metadata fetch fails\n console.error(\"Failed to fetch Mailchimp metadata:\", error);\n }\n}\n\nasync function mailchimpFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Mailchimp. Please connect your account.\");\n }\n\n if (MAILCHIMP_BASE_URL === \"https://us1.api.mailchimp.com/3.0\") {\n await initializeBaseUrl();\n }\n\n const response = await fetch(`${MAILCHIMP_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({}))) as { detail?: string };\n throw new Error(\n `Mailchimp API error: ${response.status} ${error.detail ?? response.statusText}`,\n );\n }\n\n return response.json();\n}\n\nexport async function listCampaigns(options?: {\n status?: \"save\" | \"paused\" | \"schedule\" | \"sending\" | \"sent\";\n count?: number;\n offset?: number;\n}): Promise<MailchimpCampaign[]> {\n const query = buildQueryString({\n status: options?.status,\n count: options?.count,\n offset: options?.offset,\n });\n\n const response = await mailchimpFetch<{ campaigns: MailchimpCampaign[] }>(`/campaigns${query}`);\n return response.campaigns;\n}\n\nexport async function getCampaign(campaignId: string): Promise<MailchimpCampaign> {\n return mailchimpFetch<MailchimpCampaign>(`/campaigns/${campaignId}`);\n}\n\nexport async function listLists(options?: { count?: number; offset?: number }): Promise<MailchimpList[]> {\n const query = buildQueryString({\n count: options?.count,\n offset: options?.offset,\n });\n\n const response = await mailchimpFetch<{ lists: MailchimpList[] }>(`/lists${query}`);\n return response.lists;\n}\n\nexport async function getList(listId: string): Promise<MailchimpList> {\n return mailchimpFetch<MailchimpList>(`/lists/${listId}`);\n}\n\nexport async function listMembers(\n listId: string,\n options?: {\n status?: \"subscribed\" | \"unsubscribed\" | \"cleaned\" | \"pending\" | \"transactional\";\n count?: number;\n offset?: number;\n },\n): Promise<MailchimpMember[]> {\n const query = buildQueryString({\n status: options?.status,\n count: options?.count,\n offset: options?.offset,\n });\n\n const response = await mailchimpFetch<{ members: MailchimpMember[] }>(\n `/lists/${listId}/members${query}`,\n );\n return response.members;\n}\n\nexport async function getMetadata(): Promise<MailchimpMetadata> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Mailchimp. Please connect your account.\");\n }\n\n return fetchMetadata(token);\n}\n",
|
|
651
|
+
"tools/get-campaign.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getCampaign } from \"../../lib/mailchimp-client.ts\";\n\nexport default tool({\n id: \"get-campaign\",\n description: \"Get details of a specific Mailchimp campaign by its ID.\",\n inputSchema: z.object({\n campaignId: z.string().describe(\"The ID of the campaign to retrieve\"),\n }),\n async execute({ campaignId }) {\n const campaign = await getCampaign(campaignId);\n const reportSummary = campaign.report_summary;\n\n return {\n id: campaign.id,\n webId: campaign.web_id,\n type: campaign.type,\n status: campaign.status,\n title: campaign.settings.title,\n subject: campaign.settings.subject_line,\n previewText: campaign.settings.preview_text,\n fromName: campaign.settings.from_name,\n replyTo: campaign.settings.reply_to,\n listId: campaign.recipients.list_id,\n listName: campaign.recipients.list_name,\n segmentText: campaign.recipients.segment_text,\n emailsSent: campaign.emails_sent,\n sendTime: campaign.send_time,\n createdAt: campaign.create_time,\n archiveUrl: campaign.archive_url,\n longArchiveUrl: campaign.long_archive_url,\n tracking: campaign.tracking,\n reportSummary: reportSummary\n ? {\n opens: reportSummary.opens,\n uniqueOpens: reportSummary.unique_opens,\n openRate: reportSummary.open_rate,\n clicks: reportSummary.clicks,\n subscriberClicks: reportSummary.subscriber_clicks,\n clickRate: reportSummary.click_rate,\n }\n : undefined,\n };\n },\n});\n",
|
|
652
|
+
"tools/get-list.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getList } from \"../../lib/mailchimp-client.ts\";\n\nexport default tool({\n id: \"get-list\",\n description: \"Get details of a specific Mailchimp audience list by its ID.\",\n inputSchema: z.object({\n listId: z.string().describe(\"The ID of the audience list to retrieve\"),\n }),\n async execute({ listId }) {\n const list = await getList(listId);\n const { contact, campaign_defaults, stats } = list;\n\n return {\n id: list.id,\n webId: list.web_id,\n name: list.name,\n dateCreated: list.date_created,\n listRating: list.list_rating,\n permissionReminder: list.permission_reminder,\n subscribeUrlShort: list.subscribe_url_short,\n subscribeUrlLong: list.subscribe_url_long,\n contact: {\n company: contact.company,\n address1: contact.address1,\n city: contact.city,\n state: contact.state,\n zip: contact.zip,\n country: contact.country,\n },\n campaignDefaults: {\n fromName: campaign_defaults.from_name,\n fromEmail: campaign_defaults.from_email,\n subject: campaign_defaults.subject,\n language: campaign_defaults.language,\n },\n stats: {\n memberCount: stats.member_count,\n totalContacts: stats.total_contacts,\n unsubscribeCount: stats.unsubscribe_count,\n cleanedCount: stats.cleaned_count,\n memberCountSinceSend: stats.member_count_since_send,\n unsubscribeCountSinceSend: stats.unsubscribe_count_since_send,\n cleanedCountSinceSend: stats.cleaned_count_since_send,\n campaignCount: stats.campaign_count,\n openRate: stats.open_rate,\n clickRate: stats.click_rate,\n },\n };\n },\n});\n",
|
|
653
|
+
"tools/list-campaigns.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listCampaigns } from \"../../lib/mailchimp-client.ts\";\n\nexport default tool({\n id: \"list-campaigns\",\n description:\n \"List email campaigns from Mailchimp. Can filter by status (save, paused, schedule, sending, sent).\",\n inputSchema: z.object({\n status: z\n .enum([\"save\", \"paused\", \"schedule\", \"sending\", \"sent\"])\n .optional()\n .describe(\"Filter campaigns by status\"),\n limit: z\n .number()\n .min(1)\n .max(50)\n .default(20)\n .describe(\"Maximum number of campaigns to return\"),\n }),\n async execute({ status, limit }) {\n const campaigns = await listCampaigns({ status, count: limit });\n\n return campaigns.map((campaign) => {\n const reportSummary = campaign.report_summary\n ? {\n opens: campaign.report_summary.opens,\n uniqueOpens: campaign.report_summary.unique_opens,\n openRate: campaign.report_summary.open_rate,\n clicks: campaign.report_summary.clicks,\n clickRate: campaign.report_summary.click_rate,\n }\n : undefined;\n\n return {\n id: campaign.id,\n webId: campaign.web_id,\n type: campaign.type,\n status: campaign.status,\n title: campaign.settings.title,\n subject: campaign.settings.subject_line,\n fromName: campaign.settings.from_name,\n listName: campaign.recipients.list_name,\n emailsSent: campaign.emails_sent,\n sendTime: campaign.send_time,\n createdAt: campaign.create_time,\n archiveUrl: campaign.archive_url,\n reportSummary,\n };\n });\n },\n});\n",
|
|
654
|
+
"tools/list-lists.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listLists } from \"../../lib/mailchimp-client.ts\";\n\nexport default tool({\n id: \"list-lists\",\n description: \"List all audience lists (mailing lists) in Mailchimp with their statistics.\",\n inputSchema: z.object({\n limit: z.number().min(1).max(50).default(20).describe(\"Maximum number of lists to return\"),\n }),\n async execute({ limit }) {\n const lists = await listLists({ count: limit });\n\n return lists.map((list) => {\n const { contact, campaign_defaults: campaignDefaults, stats } = list;\n\n return {\n id: list.id,\n webId: list.web_id,\n name: list.name,\n dateCreated: list.date_created,\n listRating: list.list_rating,\n subscribeUrl: list.subscribe_url_short,\n contact: {\n company: contact.company,\n city: contact.city,\n state: contact.state,\n country: contact.country,\n },\n campaignDefaults: {\n fromName: campaignDefaults.from_name,\n fromEmail: campaignDefaults.from_email,\n subject: campaignDefaults.subject,\n language: campaignDefaults.language,\n },\n stats: {\n memberCount: stats.member_count,\n totalContacts: stats.total_contacts,\n unsubscribeCount: stats.unsubscribe_count,\n cleanedCount: stats.cleaned_count,\n campaignCount: stats.campaign_count,\n openRate: stats.open_rate,\n clickRate: stats.click_rate,\n },\n };\n });\n },\n});\n",
|
|
655
|
+
"tools/list-members.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { listMembers } from \"../../lib/mailchimp-client.ts\";\n\nexport default tool({\n id: \"list-members\",\n description:\n \"List subscribers/members in a Mailchimp audience list. Can filter by subscription status.\",\n inputSchema: z.object({\n listId: z.string().describe(\"The ID of the audience list to get members from\"),\n status: z\n .enum([\"subscribed\", \"unsubscribed\", \"cleaned\", \"pending\", \"transactional\"])\n .optional()\n .describe(\"Filter members by subscription status\"),\n limit: z.number().min(1).max(50).default(20).describe(\"Maximum number of members to return\"),\n }),\n async execute({ listId, status, limit }) {\n const members = await listMembers(listId, { status, count: limit });\n\n return members.map((member) => ({\n id: member.id,\n emailAddress: member.email_address,\n uniqueEmailId: member.unique_email_id,\n contactId: member.contact_id,\n fullName: member.full_name,\n status: member.status,\n emailType: member.email_type,\n vip: member.vip,\n language: member.language,\n memberRating: member.member_rating,\n lastChanged: member.last_changed,\n timestampSignup: member.timestamp_signup,\n timestampOpt: member.timestamp_opt,\n ipSignup: member.ip_signup,\n ipOpt: member.ip_opt,\n stats: {\n avgOpenRate: member.stats.avg_open_rate,\n avgClickRate: member.stats.avg_click_rate,\n },\n mergeFields: member.merge_fields,\n tags: member.tags.map(({ id, name }) => ({ id, name })),\n location: member.location\n ? {\n countryCode: member.location.country_code,\n timezone: member.location.timezone,\n latitude: member.location.latitude,\n longitude: member.location.longitude,\n }\n : undefined,\n }));\n },\n});\n",
|
|
656
|
+
"app/api/auth/mailchimp/route.ts": "import { createOAuthInitHandler, mailchimpConfig } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(mailchimpConfig);\n",
|
|
657
|
+
"app/api/auth/mailchimp/callback/route.ts": "import { createOAuthCallbackHandler, mailchimpConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(mailchimpConfig, { tokenStore: hybridTokenStore });\n",
|
|
658
|
+
".env.example": "# Mailchimp OAuth Configuration\n# Get your credentials from https://admin.mailchimp.com/account/oauth2/\nMAILCHIMP_CLIENT_ID=your-client-id\nMAILCHIMP_CLIENT_SECRET=your-client-secret\n"
|
|
659
|
+
}
|
|
660
|
+
},
|
|
661
|
+
"integration:supabase": {
|
|
662
|
+
"files": {
|
|
663
|
+
"lib/supabase-client.ts": "import { getAnonKey, getServiceKey, getSupabaseUrl } from \"./token-store.ts\";\n\ninterface TableInfo {\n table_name: string;\n table_schema: string;\n table_type: string;\n}\n\ninterface ColumnInfo {\n column_name: string;\n data_type: string;\n is_nullable: string;\n column_default: string | null;\n}\n\ninterface QueryOptions {\n select?: string;\n filter?: Record<string, unknown>;\n order?: { column: string; ascending?: boolean };\n limit?: number;\n offset?: number;\n}\n\ninterface SupabaseError extends Error {\n code?: string;\n details?: string;\n hint?: string;\n}\n\nasync function supabaseFetch<T>(\n endpoint: string,\n options: RequestInit = {},\n useServiceRole = true,\n): Promise<T> {\n const url = getSupabaseUrl();\n const apiKey = useServiceRole ? getServiceKey() : getAnonKey();\n\n const response = await fetch(`${url}/rest/v1${endpoint}`, {\n ...options,\n headers: {\n apikey: apiKey,\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n Prefer: \"return=representation\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({}))) as Partial<SupabaseError>;\n const message =\n error.message ?? `Supabase API error: ${response.status} ${response.statusText}`;\n\n const error: SupabaseError = new Error(message);\n err.code = error.code;\n err.details = error.details;\n err.hint = error.hint;\n throw error;\n }\n\n const text = await response.text();\n return (text ? JSON.parse(text) : null) as T;\n}\n\nfunction toEqFilterValue(value: unknown): string | null {\n if (value === null) return \"is.null\";\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n return `eq.${value}`;\n }\n return null;\n}\n\nfunction buildFilterParams(filter: Record<string, unknown>): URLSearchParams {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(filter)) {\n const filterValue = toEqFilterValue(value);\n if (filterValue !== null) params.append(key, filterValue);\n }\n return params;\n}\n\n/**\n * List all tables in the public schema\n */\nexport async function listTables(): Promise<TableInfo[]> {\n try {\n const tables = await supabaseFetch<TableInfo[]>(\n \"/rpc/get_tables\",\n {\n method: \"POST\",\n body: JSON.stringify({}),\n },\n );\n return tables ?? [];\n } catch {\n const query =\n \"?select=table_name,table_schema,table_type&table_schema=eq.public&table_type=eq.BASE TABLE\";\n const tables = await supabaseFetch<TableInfo[]>(`/information_schema.tables${query}`);\n return tables ?? [];\n }\n}\n\n/**\n * Get columns for a specific table\n */\nexport async function getTableColumns(tableName: string): Promise<ColumnInfo[]> {\n const query =\n `?select=column_name,data_type,is_nullable,column_default&table_name=eq.${tableName}&table_schema=eq.public`;\n const columns = await supabaseFetch<ColumnInfo[]>(`/information_schema.columns${query}`);\n return columns ?? [];\n}\n\n/**\n * Query a table with filters, sorting, and pagination\n */\nexport async function queryTable<T = Record<string, unknown>>(\n tableName: string,\n options: QueryOptions = {},\n): Promise<T[]> {\n const params = new URLSearchParams();\n params.append(\"select\", options.select ?? \"*\");\n\n if (options.filter) {\n const filterParams = buildFilterParams(options.filter);\n for (const [key, value] of filterParams.entries()) params.append(key, value);\n }\n\n if (options.order) {\n const direction = options.order.ascending === false ? \".desc\" : \".asc\";\n params.append(\"order\", `${options.order.column}${direction}`);\n }\n\n if (options.limit) params.append(\"limit\", options.limit.toString());\n if (options.offset) params.append(\"offset\", options.offset.toString());\n\n const results = await supabaseFetch<T[]>(`/${tableName}?${params.toString()}`);\n return results ?? [];\n}\n\n/**\n * Insert a new row into a table\n */\nexport async function insertRow<T = Record<string, unknown>>(\n tableName: string,\n data: Record<string, unknown>,\n): Promise<T> {\n const result = await supabaseFetch<T[]>(\n `/${tableName}`,\n {\n method: \"POST\",\n body: JSON.stringify(data),\n },\n );\n\n if (!result?.length) throw new Error(\"Insert operation did not return data\");\n return result[0];\n}\n\n/**\n * Update a row in a table by ID\n */\nexport async function updateRow<T = Record<string, unknown>>(\n tableName: string,\n id: string | number,\n data: Record<string, unknown>,\n): Promise<T> {\n const result = await supabaseFetch<T[]>(\n `/${tableName}?id=eq.${id}`,\n {\n method: \"PATCH\",\n body: JSON.stringify(data),\n },\n );\n\n if (!result?.length) throw new Error(`No row found with id ${id}`);\n return result[0];\n}\n\n/**\n * Update rows in a table with custom filter\n */\nexport async function updateRows<T = Record<string, unknown>>(\n tableName: string,\n filter: Record<string, unknown>,\n data: Record<string, unknown>,\n): Promise<T[]> {\n const params = buildFilterParams(filter);\n\n const result = await supabaseFetch<T[]>(\n `/${tableName}?${params.toString()}`,\n {\n method: \"PATCH\",\n body: JSON.stringify(data),\n },\n );\n\n return result ?? [];\n}\n\n/**\n * Delete a row from a table by ID\n */\nexport async function deleteRow<T = Record<string, unknown>>(\n tableName: string,\n id: string | number,\n): Promise<T> {\n const result = await supabaseFetch<T[]>(\n `/${tableName}?id=eq.${id}`,\n {\n method: \"DELETE\",\n },\n );\n\n if (!result?.length) throw new Error(`No row found with id ${id}`);\n return result[0];\n}\n\n/**\n * Delete rows from a table with custom filter\n */\nexport async function deleteRows<T = Record<string, unknown>>(\n tableName: string,\n filter: Record<string, unknown>,\n): Promise<T[]> {\n const params = buildFilterParams(filter);\n\n const result = await supabaseFetch<T[]>(\n `/${tableName}?${params.toString()}`,\n {\n method: \"DELETE\",\n },\n );\n\n return result ?? [];\n}\n\n/**\n * Execute a raw SQL query using RPC\n * Note: This requires a stored procedure to be created in your Supabase database\n */\nexport function runRawQuery<T = unknown>(query: string): Promise<T> {\n return supabaseFetch<T>(\n \"/rpc/execute_sql\",\n {\n method: \"POST\",\n body: JSON.stringify({ query }),\n },\n );\n}\n\n/**\n * Get client instance (for use with @supabase/supabase-js if needed)\n */\nexport function getClient(): { url: string; anonKey: string; serviceKey: string } {\n return {\n url: getSupabaseUrl(),\n anonKey: getAnonKey(),\n serviceKey: getServiceKey(),\n };\n}\n",
|
|
664
|
+
"tools/update-row.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { updateRow, updateRows } from \"../../lib/supabase-client.ts\";\n\nexport default tool({\n id: \"update-row\",\n description: \"Update rows in a Supabase table. Can update by ID or by custom filter conditions.\",\n inputSchema: z.object({\n tableName: z.string().describe(\"The name of the table to update\"),\n id: z\n .union([z.string(), z.number()])\n .optional()\n .describe(\"The ID of the row to update (if updating a single row by ID)\"),\n filter: z\n .record(z.unknown())\n .optional()\n .describe('Filter conditions to match rows to update (e.g., {\"status\": \"pending\"})'),\n data: z.record(z.unknown()).describe(\"The data to update as key-value pairs\"),\n }),\n async execute({ tableName, id, filter, data }) {\n try {\n if (id == null && filter == null) {\n return {\n success: false,\n tableName,\n error: \"Either id or filter must be provided\",\n message: \"You must specify either an id or filter conditions to update rows\",\n };\n }\n\n if (id != null) {\n const result = await updateRow(tableName, id, data);\n return {\n success: true,\n tableName,\n rowsUpdated: 1,\n row: result,\n message: `Successfully updated row with id ${id} in ${tableName}`,\n };\n }\n\n const results = await updateRows(tableName, filter!, data);\n return {\n success: true,\n tableName,\n rowsUpdated: results.length,\n rows: results,\n message: `Successfully updated ${results.length} row(s) in ${tableName}`,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n\n return {\n success: false,\n tableName,\n error: errorMessage,\n message: `Failed to update row(s) in ${tableName}: ${errorMessage}`,\n };\n }\n },\n});\n",
|
|
665
|
+
"tools/query-table.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { queryTable } from \"../../lib/supabase-client.ts\";\n\nexport default tool({\n id: \"query-table\",\n description:\n \"Query a table in your Supabase database with optional filters, sorting, and pagination.\",\n inputSchema: z.object({\n tableName: z.string().describe(\"The name of the table to query\"),\n select: z\n .string()\n .optional()\n .describe(\n 'Columns to select (comma-separated, e.g., \"id,name,email\"). Default is all columns (*)',\n ),\n filter: z\n .record(z.unknown())\n .optional()\n .describe(\n 'Filter conditions as key-value pairs (e.g., {\"status\": \"active\", \"age\": 25})',\n ),\n orderBy: z.string().optional().describe(\"Column to order by\"),\n ascending: z\n .boolean()\n .default(true)\n .describe(\"Sort in ascending order (true) or descending (false)\"),\n limit: z\n .number()\n .min(1)\n .max(1000)\n .default(100)\n .describe(\"Maximum number of rows to return (1-1000)\"),\n offset: z\n .number()\n .min(0)\n .default(0)\n .describe(\"Number of rows to skip (for pagination)\"),\n }),\n async execute({ tableName, select, filter, orderBy, ascending, limit, offset }) {\n const results = await queryTable(tableName, {\n select,\n filter,\n order: orderBy ? { column: orderBy, ascending } : undefined,\n limit,\n offset,\n });\n\n return {\n tableName,\n count: results.length,\n rows: results,\n pagination: {\n limit,\n offset,\n hasMore: results.length === limit,\n },\n };\n },\n});\n",
|
|
666
|
+
"tools/delete-row.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { deleteRow, deleteRows } from \"../../lib/supabase-client.ts\";\n\nexport default tool({\n id: \"delete-row\",\n description:\n \"Delete rows from a Supabase table. Can delete by ID or by custom filter conditions. Returns the deleted rows.\",\n inputSchema: z.object({\n tableName: z.string().describe(\"The name of the table to delete from\"),\n id: z\n .union([z.string(), z.number()])\n .optional()\n .describe(\"The ID of the row to delete (if deleting a single row by ID)\"),\n filter: z\n .record(z.unknown())\n .optional()\n .describe('Filter conditions to match rows to delete (e.g., {\"status\": \"archived\"})'),\n confirm: z\n .boolean()\n .default(false)\n .describe(\"Confirm deletion (must be true to proceed with delete operation)\"),\n }),\n async execute({ tableName, id, filter, confirm }) {\n if (!confirm) {\n return {\n success: false,\n tableName,\n error: \"Deletion not confirmed\",\n message: \"You must set confirm: true to delete rows. This is a safety measure.\",\n };\n }\n\n if (id == null && filter == null) {\n return {\n success: false,\n tableName,\n error: \"Either id or filter must be provided\",\n message: \"You must specify either an id or filter conditions to delete rows\",\n };\n }\n\n try {\n if (id != null) {\n const result = await deleteRow(tableName, id);\n return {\n success: true,\n tableName,\n rowsDeleted: 1,\n row: result,\n message: `Successfully deleted row with id ${id} from ${tableName}`,\n };\n }\n\n const results = await deleteRows(tableName, filter!);\n return {\n success: true,\n tableName,\n rowsDeleted: results.length,\n rows: results,\n message: `Successfully deleted ${results.length} row(s) from ${tableName}`,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n\n return {\n success: false,\n tableName,\n error: errorMessage,\n message: `Failed to delete row(s) from ${tableName}: ${errorMessage}`,\n };\n }\n },\n});\n",
|
|
667
|
+
"tools/insert-row.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { insertRow } from \"../../lib/supabase-client.ts\";\n\nexport default tool({\n id: \"insert-row\",\n description: \"Insert a new row into a Supabase table. Returns the created row.\",\n inputSchema: z.object({\n tableName: z.string().describe(\"The name of the table to insert into\"),\n data: z\n .record(z.unknown())\n .describe(\"The data to insert as key-value pairs matching the table schema\"),\n }),\n async execute({ tableName, data }) {\n try {\n const row = await insertRow(tableName, data);\n\n return {\n success: true,\n tableName,\n row,\n message: `Successfully inserted row into ${tableName}`,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n\n return {\n success: false,\n tableName,\n error: errorMessage,\n message: `Failed to insert row into ${tableName}: ${errorMessage}`,\n };\n }\n },\n});\n",
|
|
668
|
+
"tools/list-tables.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getTableColumns, listTables } from \"../../lib/supabase-client.ts\";\n\nexport default tool({\n id: \"list-tables\",\n description: \"List all tables in your Supabase database with their schema information.\",\n inputSchema: z.object({\n includeColumns: z\n .boolean()\n .default(false)\n .describe(\"Include column information for each table\"),\n }),\n async execute({ includeColumns }): Promise<{\n count: number;\n tables: Array<{\n name: string;\n schema: string;\n type: string;\n columns?: Array<{\n name: string;\n type: string;\n nullable: boolean;\n default: unknown;\n }>;\n error?: string;\n }>;\n }> {\n const tables = await listTables();\n\n const baseTables = tables.map((t) => ({\n name: t.table_name,\n schema: t.table_schema,\n type: t.table_type,\n }));\n\n if (!includeColumns) {\n return { count: baseTables.length, tables: baseTables };\n }\n\n // Fetch column information for each table\n const tablesWithColumns = await Promise.all(\n tables.map(async (table) => {\n try {\n const columns = await getTableColumns(table.table_name);\n\n return {\n name: table.table_name,\n schema: table.table_schema,\n type: table.table_type,\n columns: columns.map((c) => ({\n name: c.column_name,\n type: c.data_type,\n nullable: c.is_nullable === \"YES\",\n default: c.column_default,\n })),\n };\n } catch (error) {\n return {\n name: table.table_name,\n schema: table.table_schema,\n type: table.table_type,\n columns: [],\n error: error instanceof Error ? error.message : \"Failed to fetch columns\",\n };\n }\n }),\n );\n\n return { count: tablesWithColumns.length, tables: tablesWithColumns };\n },\n});\n",
|
|
669
|
+
"app/api/auth/supabase/route.ts": "import { clearConfig, isConfigured, setSupabaseConfig } from \"../../../../lib/token-store.ts\";\n\nexport async function POST(request: Request): Promise<Response> {\n try {\n const { url, anonKey, serviceKey } = await request.json();\n\n if (!url || !anonKey || !serviceKey) {\n return Response.json(\n { error: \"Missing required fields: url, anonKey, serviceKey\" },\n { status: 400 },\n );\n }\n\n try {\n new URL(url);\n } catch {\n return Response.json({ error: \"Invalid Supabase URL format\" }, { status: 400 });\n }\n\n setSupabaseConfig({ url, anonKey, serviceKey });\n\n return Response.json({\n success: true,\n message: \"Supabase configuration saved successfully\",\n });\n } catch (error) {\n console.error(\"Supabase config error:\", error);\n return Response.json({ error: \"Failed to configure Supabase\" }, { status: 500 });\n }\n}\n\nexport function GET(): Response {\n try {\n const configured = isConfigured();\n\n return Response.json({\n configured,\n message: configured ? \"Supabase is configured\" : \"Supabase is not configured\",\n });\n } catch (error) {\n console.error(\"Supabase status check error:\", error);\n return Response.json({ error: \"Failed to check Supabase status\" }, { status: 500 });\n }\n}\n\nexport async function DELETE(): Promise<Response> {\n try {\n clearConfig();\n\n return Response.json({\n success: true,\n message: \"Supabase configuration cleared\",\n });\n } catch (error) {\n console.error(\"Supabase clear config error:\", error);\n return Response.json(\n { error: \"Failed to clear Supabase configuration\" },\n { status: 500 },\n );\n }\n}\n",
|
|
670
|
+
".env.example": "# Supabase Integration\n# Get your API keys from https://supabase.com/dashboard/project/_/settings/api\n\nSUPABASE_URL=https://xxxxx.supabase.co\nSUPABASE_ANON_KEY=your_anon_key_here\nSUPABASE_SERVICE_KEY=your_service_role_key_here\n"
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
"integration:figma": {
|
|
674
|
+
"files": {
|
|
675
|
+
"lib/figma-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst FIGMA_BASE_URL = \"https://api.figma.com/v1\";\n\nexport interface FigmaFile {\n document: FigmaNode;\n components: Record<string, FigmaComponent>;\n componentSets: Record<string, FigmaComponentSet>;\n schemaVersion: number;\n styles: Record<string, FigmaStyle>;\n name: string;\n lastModified: string;\n thumbnailUrl: string;\n version: string;\n role: string;\n editorType: string;\n linkAccess: string;\n}\n\nexport interface FigmaNode {\n id: string;\n name: string;\n type: string;\n children?: FigmaNode[];\n visible?: boolean;\n locked?: boolean;\n absoluteBoundingBox?: {\n x: number;\n y: number;\n width: number;\n height: number;\n };\n fills?: Array<{\n type: string;\n color?: {\n r: number;\n g: number;\n b: number;\n a: number;\n };\n }>;\n strokes?: unknown[];\n strokeWeight?: number;\n effects?: unknown[];\n cornerRadius?: number;\n rectangleCornerRadii?: number[];\n characters?: string;\n style?: {\n fontFamily?: string;\n fontSize?: number;\n fontWeight?: number;\n lineHeightPx?: number;\n };\n}\n\nexport interface FigmaComponent {\n key: string;\n name: string;\n description: string;\n componentSetId?: string;\n documentationLinks: unknown[];\n}\n\nexport interface FigmaComponentSet {\n key: string;\n name: string;\n description: string;\n documentationLinks: unknown[];\n}\n\nexport interface FigmaStyle {\n key: string;\n name: string;\n description: string;\n styleType: \"FILL\" | \"TEXT\" | \"EFFECT\" | \"GRID\";\n}\n\nexport interface FigmaComment {\n id: string;\n file_key: string;\n parent_id?: string;\n user: {\n id: string;\n handle: string;\n img_url: string;\n };\n created_at: string;\n resolved_at?: string;\n message: string;\n client_meta: {\n x?: number;\n y?: number;\n node_id?: string[];\n node_offset?: { x: number; y: number };\n };\n order_id: string;\n}\n\nexport interface FigmaProject {\n id: string;\n name: string;\n}\n\nexport interface FigmaTeamProject {\n id: string;\n name: string;\n}\n\nexport interface FigmaUser {\n id: string;\n handle: string;\n img_url: string;\n email?: string;\n}\n\nasync function figmaFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Figma. Please connect your account.\");\n }\n\n const response = await fetch(`${FIGMA_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (response.ok) return response.json();\n\n const error = await response.json().catch(() => ({} as { message?: string; err?: string }));\n throw new Error(\n `Figma API error: ${response.status} ${error.message || error.err || response.statusText}`,\n );\n}\n\nexport function getMe(): Promise<FigmaUser> {\n return figmaFetch<FigmaUser>(\"/me\");\n}\n\nexport function getFile(\n fileKey: string,\n options?: {\n version?: string;\n ids?: string[];\n depth?: number;\n geometry?: \"paths\" | \"bounds\";\n plugin_data?: string;\n branch_data?: boolean;\n },\n): Promise<FigmaFile> {\n const params = new URLSearchParams();\n\n if (options?.version) params.set(\"version\", options.version);\n if (options?.ids?.length) params.set(\"ids\", options.ids.join(\",\"));\n if (options?.depth) params.set(\"depth\", options.depth.toString());\n if (options?.geometry) params.set(\"geometry\", options.geometry);\n if (options?.plugin_data) params.set(\"plugin_data\", options.plugin_data);\n if (options?.branch_data) params.set(\"branch_data\", \"true\");\n\n const query = params.toString();\n return figmaFetch<FigmaFile>(`/files/${fileKey}${query ? `?${query}` : \"\"}`);\n}\n\nexport function getFileNodes(\n fileKey: string,\n nodeIds: string[],\n): Promise<{\n name: string;\n lastModified: string;\n thumbnailUrl: string;\n version: string;\n nodes: Record<string, { document: FigmaNode; components: Record<string, FigmaComponent> }>;\n}> {\n const params = new URLSearchParams({ ids: nodeIds.join(\",\") });\n return figmaFetch(`/files/${fileKey}/nodes?${params.toString()}`);\n}\n\nexport function getFileImages(\n fileKey: string,\n nodeIds: string[],\n options?: {\n format?: \"jpg\" | \"png\" | \"svg\" | \"pdf\";\n scale?: number;\n svg_include_id?: boolean;\n svg_simplify_stroke?: boolean;\n use_absolute_bounds?: boolean;\n version?: string;\n },\n): Promise<{\n err?: string;\n images: Record<string, string | null>;\n status?: number;\n}> {\n const params = new URLSearchParams({\n ids: nodeIds.join(\",\"),\n format: options?.format ?? \"png\",\n });\n\n if (options?.scale) params.set(\"scale\", options.scale.toString());\n if (options?.svg_include_id) params.set(\"svg_include_id\", \"true\");\n if (options?.svg_simplify_stroke) params.set(\"svg_simplify_stroke\", \"true\");\n if (options?.use_absolute_bounds) params.set(\"use_absolute_bounds\", \"true\");\n if (options?.version) params.set(\"version\", options.version);\n\n return figmaFetch(`/images/${fileKey}?${params.toString()}`);\n}\n\nexport function getComments(fileKey: string): Promise<{ comments: FigmaComment[] }> {\n return figmaFetch<{ comments: FigmaComment[] }>(`/files/${fileKey}/comments`);\n}\n\nexport function postComment(\n fileKey: string,\n message: string,\n options?: {\n client_meta?: { x?: number; y?: number; node_id?: string[] };\n parent_id?: string;\n },\n): Promise<FigmaComment> {\n return figmaFetch<FigmaComment>(`/files/${fileKey}/comments`, {\n method: \"POST\",\n body: JSON.stringify({\n message,\n client_meta: options?.client_meta ?? {},\n ...(options?.parent_id ? { parent_id: options.parent_id } : {}),\n }),\n });\n}\n\nexport function getTeamProjects(teamId: string): Promise<{ projects: FigmaTeamProject[] }> {\n return figmaFetch<{ projects: FigmaTeamProject[] }>(`/teams/${teamId}/projects`);\n}\n\nexport function getProjectFiles(projectId: string): Promise<{\n files: Array<{\n key: string;\n name: string;\n thumbnail_url: string;\n last_modified: string;\n }>;\n}> {\n return figmaFetch(`/projects/${projectId}/files`);\n}\n\nexport function getUserFiles(): Promise<{\n files: Array<{\n key: string;\n name: string;\n thumbnail_url: string;\n last_modified: string;\n }>;\n}> {\n throw new Error(\n \"Getting user files requires team ID. Use getTeamProjects and getProjectFiles instead.\",\n );\n}\n\nexport function extractComponents(file: FigmaFile): Array<{\n key: string;\n name: string;\n description: string;\n type: \"component\" | \"component_set\";\n}> {\n const components = Object.entries(file.components).map(([key, component]) => ({\n key,\n name: component.name,\n description: component.description,\n type: \"component\" as const,\n }));\n\n const componentSets = Object.entries(file.componentSets).map(([key, componentSet]) => ({\n key,\n name: componentSet.name,\n description: componentSet.description,\n type: \"component_set\" as const,\n }));\n\n return [...components, ...componentSets];\n}\n\nexport function extractStyles(file: FigmaFile): Array<{\n key: string;\n name: string;\n description: string;\n type: string;\n}> {\n return Object.entries(file.styles).map(([key, style]) => ({\n key,\n name: style.name,\n description: style.description,\n type: style.styleType,\n }));\n}\n\nexport function findNodesByType(node: FigmaNode, type: string): FigmaNode[] {\n const results: FigmaNode[] = [];\n if (node.type === type) results.push(node);\n\n for (const child of node.children ?? []) {\n results.push(...findNodesByType(child, type));\n }\n\n return results;\n}\n\nexport function getFileSummary(file: FigmaFile): {\n name: string;\n lastModified: string;\n componentCount: number;\n componentSetCount: number;\n styleCount: number;\n pageCount: number;\n} {\n return {\n name: file.name,\n lastModified: file.lastModified,\n componentCount: Object.keys(file.components).length,\n componentSetCount: Object.keys(file.componentSets).length,\n styleCount: Object.keys(file.styles).length,\n pageCount: file.document.children?.length ?? 0,\n };\n}\n",
|
|
676
|
+
"lib/types.ts": "export type NodeType =\n | \"DOCUMENT\"\n | \"CANVAS\"\n | \"FRAME\"\n | \"GROUP\"\n | \"VECTOR\"\n | \"BOOLEAN_OPERATION\"\n | \"STAR\"\n | \"LINE\"\n | \"ELLIPSE\"\n | \"REGULAR_POLYGON\"\n | \"RECTANGLE\"\n | \"TEXT\"\n | \"SLICE\"\n | \"COMPONENT\"\n | \"COMPONENT_SET\"\n | \"INSTANCE\";\n\nexport type BlendMode =\n | \"NORMAL\"\n | \"DARKEN\"\n | \"MULTIPLY\"\n | \"LINEAR_BURN\"\n | \"COLOR_BURN\"\n | \"LIGHTEN\"\n | \"SCREEN\"\n | \"LINEAR_DODGE\"\n | \"COLOR_DODGE\"\n | \"OVERLAY\"\n | \"SOFT_LIGHT\"\n | \"HARD_LIGHT\"\n | \"DIFFERENCE\"\n | \"EXCLUSION\"\n | \"HUE\"\n | \"SATURATION\"\n | \"COLOR\"\n | \"LUMINOSITY\";\n\nexport type EasingType = \"EASE_IN\" | \"EASE_OUT\" | \"EASE_IN_AND_OUT\" | \"LINEAR\";\n\nexport interface Vector2D {\n x: number;\n y: number;\n}\n\nexport interface Rectangle {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface Transform {\n /** 2D transformation matrix [[a, b, tx], [c, d, ty]] */\n matrix: [[number, number, number], [number, number, number]];\n}\n\nexport type PaintType =\n | \"SOLID\"\n | \"GRADIENT_LINEAR\"\n | \"GRADIENT_RADIAL\"\n | \"GRADIENT_ANGULAR\"\n | \"GRADIENT_DIAMOND\"\n | \"IMAGE\"\n | \"EMOJI\";\n\nexport interface Color {\n r: number;\n g: number;\n b: number;\n a: number;\n}\n\nexport interface ColorStop {\n position: number;\n color: Color;\n}\n\nexport interface Paint {\n type: PaintType;\n visible?: boolean;\n opacity?: number;\n color?: Color;\n blendMode?: BlendMode;\n gradientHandlePositions?: Vector2D[];\n gradientStops?: ColorStop[];\n scaleMode?: \"FILL\" | \"FIT\" | \"TILE\" | \"STRETCH\";\n imageTransform?: Transform;\n scalingFactor?: number;\n imageRef?: string;\n gifRef?: string;\n}\n\nexport type EffectType = \"INNER_SHADOW\" | \"DROP_SHADOW\" | \"LAYER_BLUR\" | \"BACKGROUND_BLUR\";\n\nexport interface Effect {\n type: EffectType;\n visible?: boolean;\n radius?: number;\n color?: Color;\n blendMode?: BlendMode;\n offset?: Vector2D;\n spread?: number;\n}\n\nexport type LayoutConstraintVertical = \"TOP\" | \"BOTTOM\" | \"CENTER\" | \"TOP_BOTTOM\" | \"SCALE\";\nexport type LayoutConstraintHorizontal = \"LEFT\" | \"RIGHT\" | \"CENTER\" | \"LEFT_RIGHT\" | \"SCALE\";\n\nexport interface LayoutConstraint {\n vertical: LayoutConstraintVertical;\n horizontal: LayoutConstraintHorizontal;\n}\n\nexport type LayoutAlign = \"MIN\" | \"CENTER\" | \"MAX\" | \"STRETCH\" | \"INHERIT\";\nexport type LayoutMode = \"NONE\" | \"HORIZONTAL\" | \"VERTICAL\";\n\nexport interface LayoutGrid {\n pattern: \"COLUMNS\" | \"ROWS\" | \"GRID\";\n sectionSize?: number;\n visible?: boolean;\n color?: Color;\n alignment?: \"MIN\" | \"MAX\" | \"CENTER\" | \"STRETCH\";\n gutterSize?: number;\n offset?: number;\n count?: number;\n}\n\nexport type TextAlignHorizontal = \"LEFT\" | \"CENTER\" | \"RIGHT\" | \"JUSTIFIED\";\nexport type TextAlignVertical = \"TOP\" | \"CENTER\" | \"BOTTOM\";\nexport type TextCase = \"ORIGINAL\" | \"UPPER\" | \"LOWER\" | \"TITLE\";\nexport type TextDecoration = \"NONE\" | \"STRIKETHROUGH\" | \"UNDERLINE\";\n\nexport interface TypeStyle {\n fontFamily: string;\n fontPostScriptName?: string;\n paragraphSpacing?: number;\n paragraphIndent?: number;\n italic?: boolean;\n fontWeight: number;\n fontSize: number;\n textAlignHorizontal?: TextAlignHorizontal;\n textAlignVertical?: TextAlignVertical;\n letterSpacing?: number;\n fills?: Paint[];\n lineHeightPx?: number;\n lineHeightPercent?: number;\n lineHeightPercentFontSize?: number;\n lineHeightUnit?: \"PIXELS\" | \"FONT_SIZE_%\" | \"INTRINSIC_%\";\n}\n\nexport interface Component {\n key: string;\n name: string;\n description: string;\n componentSetId?: string;\n documentationLinks: string[];\n remote?: boolean;\n}\n\nexport interface ComponentSet {\n key: string;\n name: string;\n description: string;\n documentationLinks: string[];\n remote?: boolean;\n}\n\nexport type StyleType = \"FILL\" | \"TEXT\" | \"EFFECT\" | \"GRID\";\n\nexport interface Style {\n key: string;\n name: string;\n description: string;\n styleType: StyleType;\n remote?: boolean;\n}\n\nexport type ExportFormat = \"JPG\" | \"PNG\" | \"SVG\" | \"PDF\";\n\nexport interface ExportSettings {\n suffix: string;\n format: ExportFormat;\n constraint?: {\n type: \"SCALE\" | \"WIDTH\" | \"HEIGHT\";\n value: number;\n };\n}\n\nexport interface Comment {\n id: string;\n file_key: string;\n parent_id?: string;\n user: User;\n created_at: string;\n resolved_at?: string;\n message: string;\n client_meta: CommentClientMeta;\n order_id: string;\n}\n\nexport interface CommentClientMeta {\n x?: number;\n y?: number;\n node_id?: string[];\n node_offset?: Vector2D;\n}\n\nexport interface User {\n id: string;\n handle: string;\n img_url: string;\n email?: string;\n}\n\nexport interface FileResponse {\n document: Node;\n components: Record<string, Component>;\n componentSets: Record<string, ComponentSet>;\n schemaVersion: number;\n styles: Record<string, Style>;\n name: string;\n lastModified: string;\n thumbnailUrl: string;\n version: string;\n role: \"owner\" | \"editor\" | \"viewer\";\n editorType: \"figma\" | \"figjam\";\n linkAccess: \"view\" | \"edit\" | \"org_view\" | \"org_edit\";\n}\n\nexport interface NodeBase {\n id: string;\n name: string;\n visible?: boolean;\n type: NodeType;\n pluginData?: unknown;\n sharedPluginData?: unknown;\n locked?: boolean;\n}\n\nexport interface NodeWithChildren extends NodeBase {\n children: Node[];\n}\n\nexport interface DocumentNode extends NodeWithChildren {\n type: \"DOCUMENT\";\n}\n\nexport interface CanvasNode extends NodeWithChildren {\n type: \"CANVAS\";\n backgroundColor: Color;\n prototypeStartNodeID?: string;\n prototypeDevice?: {\n type: string;\n rotation: \"NONE\" | \"CCW_90\";\n };\n exportSettings?: ExportSettings[];\n}\n\nexport interface FrameNode extends NodeWithChildren {\n type: \"FRAME\";\n absoluteBoundingBox?: Rectangle;\n absoluteRenderBounds?: Rectangle;\n constraints?: LayoutConstraint;\n clipsContent?: boolean;\n background: Paint[];\n backgroundColor?: Color;\n fills?: Paint[];\n strokes?: Paint[];\n strokeWeight?: number;\n strokeAlign?: \"INSIDE\" | \"OUTSIDE\" | \"CENTER\";\n strokeDashes?: number[];\n cornerRadius?: number;\n rectangleCornerRadii?: [number, number, number, number];\n exportSettings?: ExportSettings[];\n blendMode?: BlendMode;\n preserveRatio?: boolean;\n layoutAlign?: LayoutAlign;\n layoutGrow?: number;\n layoutMode?: LayoutMode;\n primaryAxisSizingMode?: \"FIXED\" | \"AUTO\";\n counterAxisSizingMode?: \"FIXED\" | \"AUTO\";\n primaryAxisAlignItems?: LayoutAlign;\n counterAxisAlignItems?: LayoutAlign;\n paddingLeft?: number;\n paddingRight?: number;\n paddingTop?: number;\n paddingBottom?: number;\n itemSpacing?: number;\n layoutGrids?: LayoutGrid[];\n effects?: Effect[];\n isMask?: boolean;\n isMaskOutline?: boolean;\n transitionNodeID?: string;\n transitionDuration?: number;\n transitionEasing?: EasingType;\n opacity?: number;\n}\n\nexport interface GroupNode extends NodeWithChildren {\n type: \"GROUP\";\n absoluteBoundingBox?: Rectangle;\n absoluteRenderBounds?: Rectangle;\n constraints?: LayoutConstraint;\n clipsContent?: boolean;\n blendMode?: BlendMode;\n effects?: Effect[];\n opacity?: number;\n}\n\nexport interface VectorNode extends NodeBase {\n type: \"VECTOR\" | \"BOOLEAN_OPERATION\" | \"STAR\" | \"LINE\" | \"ELLIPSE\" | \"REGULAR_POLYGON\" | \"RECTANGLE\";\n absoluteBoundingBox?: Rectangle;\n absoluteRenderBounds?: Rectangle;\n constraints?: LayoutConstraint;\n fills?: Paint[];\n fillGeometry?: unknown[];\n strokes?: Paint[];\n strokeWeight?: number;\n strokeCap?: \"NONE\" | \"ROUND\" | \"SQUARE\" | \"LINE_ARROW\" | \"TRIANGLE_ARROW\";\n strokeJoin?: \"MITER\" | \"BEVEL\" | \"ROUND\";\n strokeDashes?: number[];\n strokeAlign?: \"INSIDE\" | \"OUTSIDE\" | \"CENTER\";\n strokeGeometry?: unknown[];\n cornerRadius?: number;\n rectangleCornerRadii?: [number, number, number, number];\n exportSettings?: ExportSettings[];\n blendMode?: BlendMode;\n preserveRatio?: boolean;\n layoutAlign?: LayoutAlign;\n layoutGrow?: number;\n effects?: Effect[];\n isMask?: boolean;\n opacity?: number;\n}\n\nexport interface TextNode extends NodeBase {\n type: \"TEXT\";\n absoluteBoundingBox?: Rectangle;\n absoluteRenderBounds?: Rectangle;\n constraints?: LayoutConstraint;\n fills?: Paint[];\n strokes?: Paint[];\n strokeWeight?: number;\n strokeAlign?: \"INSIDE\" | \"OUTSIDE\" | \"CENTER\";\n strokeDashes?: number[];\n exportSettings?: ExportSettings[];\n blendMode?: BlendMode;\n preserveRatio?: boolean;\n layoutAlign?: LayoutAlign;\n layoutGrow?: number;\n effects?: Effect[];\n characters: string;\n style: TypeStyle;\n characterStyleOverrides?: number[];\n styleOverrideTable?: Record<number, TypeStyle>;\n opacity?: number;\n}\n\nexport interface ComponentNode extends FrameNode {\n type: \"COMPONENT\";\n}\n\nexport interface ComponentSetNode extends FrameNode {\n type: \"COMPONENT_SET\";\n}\n\nexport interface InstanceNode extends FrameNode {\n type: \"INSTANCE\";\n componentId: string;\n overrides?: unknown[];\n}\n\nexport type Node =\n | DocumentNode\n | CanvasNode\n | FrameNode\n | GroupNode\n | VectorNode\n | TextNode\n | ComponentNode\n | ComponentSetNode\n | InstanceNode;\n\nexport interface Project {\n id: string;\n name: string;\n}\n\nexport interface FileReference {\n key: string;\n name: string;\n thumbnail_url: string;\n last_modified: string;\n}\n\nexport interface ProjectFilesResponse {\n files: FileReference[];\n}\n\nexport interface TeamProjectsResponse {\n projects: Project[];\n}\n\nexport interface Version {\n id: string;\n created_at: string;\n label?: string;\n description?: string;\n user: User;\n thumbnail_url?: string;\n}\n\nexport interface VersionsResponse {\n versions: Version[];\n pagination?: {\n next_page?: number;\n };\n}\n",
|
|
677
|
+
"tools/get-file.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport {\n extractComponents,\n extractStyles,\n getFile,\n getFileSummary,\n} from \"../../lib/figma-client.ts\";\n\nexport default tool({\n id: \"get-file\",\n description:\n \"Get detailed information about a Figma file including components, styles, and structure. Returns file metadata, component list, and style information.\",\n inputSchema: z.object({\n fileKey: z.string().describe(\"The file key (from the Figma URL)\"),\n includeComponents: z\n .boolean()\n .default(true)\n .describe(\"Include component information\"),\n includeStyles: z.boolean().default(true).describe(\"Include style information\"),\n depth: z\n .number()\n .min(1)\n .max(10)\n .optional()\n .describe(\"Depth of nodes to traverse (default: all)\"),\n }),\n async execute({ fileKey, includeComponents, includeStyles, depth }) {\n const file = await getFile(fileKey, { depth });\n const summary = getFileSummary(file);\n\n const result = {\n summary,\n url: `https://www.figma.com/file/${fileKey}`,\n thumbnailUrl: file.thumbnailUrl,\n pages: (file.document.children ?? []).map((page) => ({\n id: page.id,\n name: page.name,\n type: page.type,\n })),\n ...(includeComponents ? { components: extractComponents(file) } : {}),\n ...(includeStyles ? { styles: extractStyles(file) } : {}),\n };\n\n return result;\n },\n});\n",
|
|
678
|
+
"tools/list-files.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getProjectFiles, getTeamProjects } from \"../../lib/figma-client.ts\";\n\nexport default tool({\n id: \"list-files\",\n description:\n \"List Figma files in a team project. Returns file names, keys, thumbnails, and last modified dates.\",\n inputSchema: z.object({\n teamId: z.string().describe(\"The team ID to list projects from\"),\n projectId: z\n .string()\n .optional()\n .describe(\"Optional project ID to filter files. If not provided, lists all projects\"),\n limit: z.number().min(1).max(50).default(20).describe(\"Maximum number of files to return\"),\n }),\n async execute({ teamId, projectId, limit }) {\n if (projectId) {\n const response = await getProjectFiles(projectId);\n return response.files.slice(0, limit).map((file) => ({\n key: file.key,\n name: file.name,\n thumbnailUrl: file.thumbnail_url,\n lastModified: file.last_modified,\n url: `https://www.figma.com/file/${file.key}`,\n }));\n }\n\n const response = await getTeamProjects(teamId);\n return {\n projects: response.projects.slice(0, limit).map((project) => ({\n id: project.id,\n name: project.name,\n })),\n message: \"Use project IDs to get files with the projectId parameter\",\n };\n },\n});\n",
|
|
679
|
+
"tools/get-comments.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getComments } from \"../../lib/figma-client.ts\";\n\nexport default tool({\n id: \"get-comments\",\n description:\n \"Get all comments on a Figma file. Returns comment threads with messages, authors, timestamps, and resolution status.\",\n inputSchema: z.object({\n fileKey: z.string().describe(\"The file key (from the Figma URL)\"),\n includeResolved: z.boolean().default(false).describe(\"Include resolved comments\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(50)\n .describe(\"Maximum number of comments to return\"),\n }),\n async execute({ fileKey, includeResolved, limit }): Promise<{\n totalComments: number;\n unresolvedCount: number;\n resolvedCount: number;\n threads: Array<{\n rootComment: {\n id: string;\n message: string;\n author: { handle: string; avatar: string };\n createdAt: string;\n resolvedAt: string | null;\n isResolved: boolean;\n parentId: string | null;\n isReply: boolean;\n location: { nodeIds: string; x: number; y: number } | null;\n };\n replies: Array<{\n id: string;\n message: string;\n author: { handle: string; avatar: string };\n createdAt: string;\n resolvedAt: string | null;\n isResolved: boolean;\n parentId: string | null;\n isReply: boolean;\n location: { nodeIds: string; x: number; y: number } | null;\n }>;\n }>;\n fileUrl: string;\n }> {\n const response = await getComments(fileKey);\n const allComments = includeResolved\n ? response.comments\n : response.comments.filter((comment) => !comment.resolved_at);\n\n const comments = allComments.slice(0, limit);\n\n const formattedComments = comments.map((comment) => ({\n id: comment.id,\n message: comment.message,\n author: {\n handle: comment.user.handle,\n avatar: comment.user.img_url,\n },\n createdAt: comment.created_at,\n resolvedAt: comment.resolved_at,\n isResolved: !!comment.resolved_at,\n parentId: comment.parent_id,\n isReply: !!comment.parent_id,\n location: comment.client_meta.node_id\n ? {\n nodeIds: comment.client_meta.node_id,\n x: comment.client_meta.x,\n y: comment.client_meta.y,\n }\n : null,\n }));\n\n const rootComments = formattedComments.filter((c) => !c.isReply);\n const repliesByParentId = new Map<string, typeof formattedComments>();\n\n for (const comment of formattedComments) {\n if (!comment.parentId) continue;\n const existing = repliesByParentId.get(comment.parentId);\n if (existing) {\n existing.push(comment);\n } else {\n repliesByParentId.set(comment.parentId, [comment]);\n }\n }\n\n const threads = rootComments.map((root) => ({\n rootComment: root,\n replies: repliesByParentId.get(root.id) ?? [],\n }));\n\n let unresolvedCount = 0;\n let resolvedCount = 0;\n\n for (const comment of comments) {\n if (comment.resolved_at) resolvedCount += 1;\n else unresolvedCount += 1;\n }\n\n return {\n totalComments: comments.length,\n unresolvedCount,\n resolvedCount,\n threads,\n fileUrl: `https://www.figma.com/file/${fileKey}`,\n };\n },\n});\n",
|
|
680
|
+
"tools/list-projects.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getProjectFiles, getTeamProjects } from \"../../lib/figma-client.ts\";\n\nexport default tool({\n id: \"list-projects\",\n description:\n \"List all projects in a Figma team. Optionally include file counts and recent files for each project.\",\n inputSchema: z.object({\n teamId: z.string().describe(\"The team ID to list projects from\"),\n includeFiles: z.boolean().default(false).describe(\"Include recent files for each project\"),\n filesPerProject: z\n .number()\n .min(1)\n .max(10)\n .default(5)\n .describe(\"Number of recent files to include per project (if includeFiles is true)\"),\n limit: z.number().min(1).max(50).default(20).describe(\"Maximum number of projects to return\"),\n }),\n async execute({ teamId, includeFiles, filesPerProject, limit }) {\n const { projects: allProjects } = await getTeamProjects(teamId);\n const projects = allProjects.slice(0, limit);\n\n if (!includeFiles) {\n return {\n projects: projects.map(({ id, name }) => ({ id, name })),\n };\n }\n\n const projectsWithFiles = await Promise.all(\n projects.map(async ({ id, name }) => {\n try {\n const { files } = await getProjectFiles(id);\n const recentFiles = files.slice(0, filesPerProject).map((file) => ({\n key: file.key,\n name: file.name,\n thumbnailUrl: file.thumbnail_url,\n lastModified: file.last_modified,\n url: `https://www.figma.com/file/${file.key}`,\n }));\n\n return {\n id,\n name,\n fileCount: files.length,\n recentFiles,\n };\n } catch (error) {\n return {\n id,\n name,\n fileCount: 0,\n recentFiles: [],\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n );\n\n return {\n projects: projectsWithFiles,\n totalProjects: projects.length,\n };\n },\n});\n",
|
|
681
|
+
"tools/post-comment.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { postComment } from \"../../lib/figma-client.ts\";\n\nexport default tool({\n id: \"post-comment\",\n description:\n \"Post a comment on a Figma file. Can be a new comment or a reply to an existing comment thread.\",\n inputSchema: z.object({\n fileKey: z.string().describe(\"The file key (from the Figma URL)\"),\n message: z.string().min(1).describe(\"The comment message to post\"),\n parentId: z\n .string()\n .optional()\n .describe(\"ID of parent comment to reply to (for threaded replies)\"),\n nodeId: z.string().optional().describe(\"ID of the Figma node to attach the comment to\"),\n x: z.number().optional().describe(\"X coordinate for comment placement (0-1, relative to canvas)\"),\n y: z.number().optional().describe(\"Y coordinate for comment placement (0-1, relative to canvas)\"),\n }),\n async execute({ fileKey, message, parentId, nodeId, x, y }) {\n const clientMeta: { x?: number; y?: number; node_id?: string[] } = {\n ...(x !== undefined ? { x } : {}),\n ...(y !== undefined ? { y } : {}),\n ...(nodeId ? { node_id: [nodeId] } : {}),\n };\n\n const comment = await postComment(fileKey, message, {\n client_meta: Object.keys(clientMeta).length ? clientMeta : undefined,\n parent_id: parentId,\n });\n\n return {\n success: true,\n comment: {\n id: comment.id,\n message: comment.message,\n author: {\n handle: comment.user.handle,\n avatar: comment.user.img_url,\n },\n createdAt: comment.created_at,\n isReply: Boolean(comment.parent_id),\n fileUrl: `https://www.figma.com/file/${fileKey}`,\n },\n };\n },\n});\n",
|
|
682
|
+
"app/api/auth/figma/route.ts": "import { createOAuthInitHandler, figmaConfig, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(figmaConfig, { tokenStore: memoryTokenStore });\n",
|
|
683
|
+
"app/api/auth/figma/callback/route.ts": "import { createOAuthCallbackHandler, figmaConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(figmaConfig, { tokenStore: hybridTokenStore });\n",
|
|
684
|
+
".env.example": "# Figma OAuth Integration\n# Get these credentials from https://www.figma.com/developers/apps\n\nFIGMA_CLIENT_ID=your_figma_client_id\nFIGMA_CLIENT_SECRET=your_figma_client_secret\n"
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
"integration:twilio": {
|
|
688
|
+
"files": {
|
|
689
|
+
"lib/twilio-client.ts": "import { getTwilioCredentials } from \"./token-store.ts\";\n\nconst TWILIO_API_VERSION = \"2010-04-01\";\n\nexport interface TwilioMessage {\n sid: string;\n account_sid: string;\n from: string;\n to: string;\n body: string;\n status: \"queued\" | \"sending\" | \"sent\" | \"failed\" | \"delivered\" | \"undelivered\" | \"receiving\" | \"received\";\n direction: \"inbound\" | \"outbound-api\" | \"outbound-call\" | \"outbound-reply\";\n date_created: string;\n date_updated: string;\n date_sent: string | null;\n price: string | null;\n price_unit: string | null;\n error_code: number | null;\n error_message: string | null;\n uri: string;\n num_segments: string;\n num_media: string;\n messaging_service_sid: string | null;\n}\n\nexport interface TwilioCall {\n sid: string;\n account_sid: string;\n from: string;\n to: string;\n status: \"queued\" | \"ringing\" | \"in-progress\" | \"completed\" | \"busy\" | \"failed\" | \"no-answer\" | \"canceled\";\n direction: \"inbound\" | \"outbound-api\" | \"outbound-dial\";\n date_created: string;\n date_updated: string;\n start_time: string | null;\n end_time: string | null;\n duration: string | null;\n price: string | null;\n price_unit: string | null;\n uri: string;\n answered_by: string | null;\n}\n\nexport interface TwilioListResponse<T> {\n messages?: T[];\n calls?: T[];\n first_page_uri: string;\n next_page_uri: string | null;\n previous_page_uri: string | null;\n uri: string;\n page: number;\n page_size: number;\n}\n\ninterface TwilioErrorResponse {\n code: number;\n message: string;\n more_info: string;\n status: number;\n}\n\nfunction buildParams(params: Record<string, string | number>): string {\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n search.append(key, String(value));\n }\n return search.toString();\n}\n\nfunction addMediaUrls(params: Record<string, string>, mediaUrl?: string[]): void {\n if (!mediaUrl?.length) return;\n\n mediaUrl.forEach((url, index) => {\n params[`MediaUrl[${index}]`] = url;\n });\n}\n\nfunction ensureTwilioCredentials(): NonNullable<ReturnType<typeof getTwilioCredentials>> {\n const credentials = getTwilioCredentials();\n if (!credentials) throw new Error(\"Twilio credentials not configured\");\n return credentials;\n}\n\nasync function twilioFetch<T>(\n endpoint: string,\n options: RequestInit & { params?: Record<string, string | number> } = {},\n): Promise<T> {\n const credentials = getTwilioCredentials();\n if (!credentials) {\n throw new Error(\n \"Twilio not configured. Please set TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_PHONE_NUMBER environment variables.\",\n );\n }\n\n const { accountSid, authToken } = credentials;\n const baseUrl = `https://api.twilio.com/${TWILIO_API_VERSION}/Accounts/${accountSid}`;\n let url = `${baseUrl}${endpoint}`;\n\n const headers: Record<string, string> = {\n Authorization: `Basic ${btoa(`${accountSid}:${authToken}`)}`,\n };\n\n let body: string | undefined;\n if (options.params) {\n const encoded = buildParams(options.params);\n if (options.method === \"POST\") {\n body = encoded;\n headers[\"Content-Type\"] = \"application/x-www-form-urlencoded\";\n } else {\n url += `?${encoded}`;\n }\n }\n\n const response = await fetch(url, { ...options, headers, body });\n const data = await response.json();\n\n if (!response.ok) {\n const error = data as TwilioErrorResponse;\n throw new Error(`Twilio API error (${error.code}): ${error.message}\\nMore info: ${error.more_info}`);\n }\n\n return data as T;\n}\n\nexport async function sendSMS(\n to: string,\n body: string,\n options?: {\n mediaUrl?: string[];\n statusCallback?: string;\n },\n): Promise<TwilioMessage> {\n const { phoneNumber } = ensureTwilioCredentials();\n\n const params: Record<string, string> = {\n To: to,\n From: phoneNumber,\n Body: body,\n };\n\n addMediaUrls(params, options?.mediaUrl);\n\n if (options?.statusCallback) params.StatusCallback = options.statusCallback;\n\n return twilioFetch<TwilioMessage>(\"/Messages.json\", { method: \"POST\", params });\n}\n\nexport async function sendWhatsApp(\n to: string,\n body: string,\n options?: {\n mediaUrl?: string[];\n statusCallback?: string;\n },\n): Promise<TwilioMessage> {\n const { phoneNumber } = ensureTwilioCredentials();\n\n const whatsappTo = to.startsWith(\"whatsapp:\") ? to : `whatsapp:${to}`;\n const whatsappFrom = phoneNumber.startsWith(\"whatsapp:\") ? phoneNumber : `whatsapp:${phoneNumber}`;\n\n const params: Record<string, string> = {\n To: whatsappTo,\n From: whatsappFrom,\n Body: body,\n };\n\n addMediaUrls(params, options?.mediaUrl);\n\n if (options?.statusCallback) params.StatusCallback = options.statusCallback;\n\n return twilioFetch<TwilioMessage>(\"/Messages.json\", { method: \"POST\", params });\n}\n\nexport async function listMessages(options?: {\n to?: string;\n from?: string;\n dateSent?: string;\n limit?: number;\n}): Promise<TwilioMessage[]> {\n const params: Record<string, string | number> = {};\n\n if (options?.to) params.To = options.to;\n if (options?.from) params.From = options.from;\n if (options?.dateSent) params.DateSent = options.dateSent;\n if (options?.limit) params.PageSize = options.limit;\n\n const response = await twilioFetch<TwilioListResponse<TwilioMessage>>(\"/Messages.json\", { params });\n return response.messages ?? [];\n}\n\nexport function getMessage(messageSid: string): Promise<TwilioMessage> {\n return twilioFetch<TwilioMessage>(`/Messages/${messageSid}.json`);\n}\n\nexport async function listCalls(options?: {\n to?: string;\n from?: string;\n status?: \"queued\" | \"ringing\" | \"in-progress\" | \"completed\" | \"busy\" | \"failed\" | \"no-answer\" | \"canceled\";\n startTime?: string;\n limit?: number;\n}): Promise<TwilioCall[]> {\n const params: Record<string, string | number> = {};\n\n if (options?.to) params.To = options.to;\n if (options?.from) params.From = options.from;\n if (options?.status) params.Status = options.status;\n if (options?.startTime) params.StartTime = options.startTime;\n if (options?.limit) params.PageSize = options.limit;\n\n const response = await twilioFetch<TwilioListResponse<TwilioCall>>(\"/Calls.json\", { params });\n return response.calls ?? [];\n}\n\nexport function getCall(callSid: string): Promise<TwilioCall> {\n return twilioFetch<TwilioCall>(`/Calls/${callSid}.json`);\n}\n\nexport async function makeCall(\n to: string,\n twiml: string,\n options?: {\n twimlUrl?: string;\n statusCallback?: string;\n statusCallbackMethod?: \"GET\" | \"POST\";\n timeout?: number;\n },\n): Promise<TwilioCall> {\n const { phoneNumber } = ensureTwilioCredentials();\n\n const params: Record<string, string | number> = {\n To: to,\n From: phoneNumber,\n };\n\n if (options?.twimlUrl) params.Url = options.twimlUrl;\n else params.Twiml = twiml;\n\n if (options?.statusCallback) params.StatusCallback = options.statusCallback;\n if (options?.statusCallbackMethod) params.StatusCallbackMethod = options.statusCallbackMethod;\n if (options?.timeout) params.Timeout = options.timeout;\n\n return twilioFetch<TwilioCall>(\"/Calls.json\", { method: \"POST\", params });\n}\n\nexport function formatPhoneNumber(phone: string, defaultCountryCode = \"+1\"): string {\n const digits = phone.replace(/\\D/g, \"\");\n\n if (phone.startsWith(\"+\")) return phone;\n if (digits.length === 10) return `${defaultCountryCode}${digits}`;\n if (digits.length === 11 && digits.startsWith(\"1\")) return `+${digits}`;\n return `+${digits}`;\n}\n\nexport function formatDate(date: Date): string {\n return date.toISOString().split(\"T\")[0];\n}\n\nexport function parseDate(dateString: string): Date {\n return new Date(dateString);\n}\n",
|
|
690
|
+
"tools/send-sms.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatPhoneNumber, sendSMS } from \"../../lib/twilio-client.ts\";\n\nexport default tool({\n id: \"send-sms\",\n description: \"Send an SMS text message to a phone number using Twilio\",\n inputSchema: z.object({\n to: z\n .string()\n .describe(\n \"Recipient phone number in E.164 format (e.g., +14155552671) or 10-digit US format\",\n ),\n body: z\n .string()\n .min(1)\n .max(1600)\n .describe(\"Message text to send (max 1600 characters)\"),\n mediaUrl: z\n .array(z.string().url())\n .optional()\n .describe(\"Optional array of media URLs to send as MMS (images, videos, etc.)\"),\n }),\n execute: async ({ to, body, mediaUrl }) => {\n try {\n const formattedPhone = formatPhoneNumber(to);\n const message = await sendSMS(formattedPhone, body, { mediaUrl });\n\n return {\n success: true,\n messageSid: message.sid,\n status: message.status,\n to: message.to,\n from: message.from,\n body: message.body,\n numSegments: message.num_segments,\n price: message.price,\n priceUnit: message.price_unit,\n message: `SMS sent successfully to ${message.to}. Status: ${message.status}`,\n };\n } catch (error) {\n if (!(error instanceof Error)) throw error;\n\n const msg = error.message;\n\n if (msg.includes(\"not configured\")) {\n return {\n error:\n \"Twilio not configured. Please set TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_PHONE_NUMBER.\",\n setupUrl: \"https://console.twilio.com/\",\n };\n }\n\n if (msg.includes(\"21211\")) {\n return {\n error: `Invalid phone number: ${to}. Please use E.164 format (e.g., +14155552671).`,\n };\n }\n\n if (msg.includes(\"21608\")) {\n return {\n error:\n \"Unverified number. Trial accounts can only send to verified numbers. Verify at: https://console.twilio.com/us1/develop/phone-numbers/manage/verified\",\n };\n }\n\n if (msg.includes(\"21610\")) {\n return {\n error:\n \"Unverified 'To' number. This number must be verified before you can send messages to it during trial.\",\n };\n }\n\n throw error;\n }\n },\n});\n",
|
|
691
|
+
"tools/get-message.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { getMessage } from \"../../lib/twilio-client.ts\";\n\nexport default tool({\n id: \"get-message\",\n description:\n \"Get detailed information about a specific SMS or WhatsApp message by its SID (Message ID)\",\n inputSchema: z.object({\n messageSid: z\n .string()\n .describe(\"The unique Twilio Message SID (starts with 'MM' or 'SM', e.g., MM1234567890abcdef)\"),\n }),\n execute: async ({ messageSid }) => {\n try {\n const message = await getMessage(messageSid);\n\n return {\n success: true,\n message: {\n sid: message.sid,\n accountSid: message.account_sid,\n direction: message.direction,\n from: message.from,\n to: message.to,\n body: message.body,\n status: message.status,\n dateSent: message.date_sent,\n dateCreated: message.date_created,\n dateUpdated: message.date_updated,\n numSegments: message.num_segments,\n numMedia: message.num_media,\n price: message.price ? `${message.price} ${message.price_unit}` : null,\n errorCode: message.error_code,\n errorMessage: message.error_message,\n uri: message.uri,\n messagingServiceSid: message.messaging_service_sid,\n },\n summary: `Message ${message.sid}: ${message.direction} ${message.status} message from ${message.from} to ${message.to}`,\n };\n } catch (error) {\n if (!(error instanceof Error)) throw error;\n\n const message = error.message;\n\n if (message.includes(\"not configured\")) {\n return {\n error:\n \"Twilio not configured. Please set TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_PHONE_NUMBER.\",\n setupUrl: \"https://console.twilio.com/\",\n };\n }\n\n if (message.includes(\"20404\")) {\n return {\n error: `Message not found. The SID '${messageSid}' does not exist in your account.`,\n };\n }\n\n throw error;\n }\n },\n});\n",
|
|
692
|
+
"tools/send-whatsapp.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatPhoneNumber, sendWhatsApp } from \"../../lib/twilio-client.ts\";\n\nexport default tool({\n id: \"send-whatsapp\",\n description:\n \"Send a WhatsApp message to a phone number using Twilio. Note: Recipients must have opted in to receive messages.\",\n inputSchema: z.object({\n to: z\n .string()\n .describe(\n \"Recipient phone number in E.164 format (e.g., +14155552671). The 'whatsapp:' prefix is optional.\",\n ),\n body: z.string().min(1).describe(\"Message text to send\"),\n mediaUrl: z\n .array(z.string().url())\n .optional()\n .describe(\"Optional array of media URLs to send (images, videos, PDFs, etc.)\"),\n }),\n execute: async ({ to, body, mediaUrl }) => {\n try {\n const formattedPhone = formatPhoneNumber(to);\n const message = await sendWhatsApp(formattedPhone, body, { mediaUrl });\n\n return {\n success: true,\n messageSid: message.sid,\n status: message.status,\n to: message.to,\n from: message.from,\n body: message.body,\n numSegments: message.num_segments,\n price: message.price,\n priceUnit: message.price_unit,\n message: `WhatsApp message sent successfully to ${message.to}. Status: ${message.status}`,\n };\n } catch (error) {\n if (!(error instanceof Error)) throw error;\n\n const { message } = error;\n\n if (message.includes(\"not configured\")) {\n return {\n error:\n \"Twilio not configured. Please set TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_PHONE_NUMBER.\",\n setupUrl: \"https://console.twilio.com/\",\n };\n }\n\n if (message.includes(\"63007\")) {\n return {\n error:\n \"Recipient has not opted in to receive WhatsApp messages. They must send a message to your WhatsApp sandbox first.\",\n sandboxUrl: \"https://console.twilio.com/us1/develop/sms/try-it-out/whatsapp-learn\",\n };\n }\n\n if (message.includes(\"63016\")) {\n return {\n error: \"WhatsApp message failed: Recipient phone number is not a WhatsApp user.\",\n };\n }\n\n if (message.includes(\"63030\")) {\n return {\n error: \"Message body is required for WhatsApp messages unless media is included.\",\n };\n }\n\n if (message.includes(\"63003\")) {\n return {\n error: \"Message exceeds maximum allowed length for WhatsApp.\",\n };\n }\n\n throw error;\n }\n },\n});\n",
|
|
693
|
+
"tools/list-messages.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatPhoneNumber, listMessages } from \"../../lib/twilio-client.ts\";\n\nexport default tool({\n id: \"list-messages\",\n description:\n \"List recent SMS and WhatsApp messages from your Twilio account. Supports filtering by recipient, sender, and date.\",\n inputSchema: z.object({\n to: z\n .string()\n .optional()\n .describe(\"Filter by recipient phone number in E.164 format (e.g., +14155552671)\"),\n from: z\n .string()\n .optional()\n .describe(\"Filter by sender phone number in E.164 format (e.g., +14155552671)\"),\n dateSent: z\n .string()\n .optional()\n .describe(\"Filter by date sent in YYYY-MM-DD format (e.g., 2024-01-15)\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .optional()\n .describe(\"Maximum number of messages to return (default: 20, max: 100)\"),\n }),\n execute: async ({ to, from, dateSent, limit }) => {\n try {\n const options: { to?: string; from?: string; dateSent?: string; limit?: number } = {\n to: to ? formatPhoneNumber(to) : undefined,\n from: from ? formatPhoneNumber(from) : undefined,\n dateSent,\n limit,\n };\n\n const messages = await listMessages(options);\n\n if (messages.length === 0) {\n return {\n success: true,\n count: 0,\n messages: [],\n message: \"No messages found matching the criteria.\",\n };\n }\n\n const formattedMessages = messages.map((msg) => ({\n sid: msg.sid,\n direction: msg.direction,\n from: msg.from,\n to: msg.to,\n body: msg.body,\n status: msg.status,\n dateSent: msg.date_sent,\n dateCreated: msg.date_created,\n numSegments: msg.num_segments,\n numMedia: msg.num_media,\n price: msg.price ? `${msg.price} ${msg.price_unit}` : null,\n errorCode: msg.error_code,\n errorMessage: msg.error_message,\n }));\n\n return {\n success: true,\n count: messages.length,\n messages: formattedMessages,\n message: `Found ${messages.length} message${messages.length === 1 ? \"\" : \"s\"}.`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not configured\")) {\n return {\n error:\n \"Twilio not configured. Please set TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_PHONE_NUMBER.\",\n setupUrl: \"https://console.twilio.com/\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
694
|
+
"tools/list-calls.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatPhoneNumber, listCalls } from \"../../lib/twilio-client.ts\";\n\ntype CallStatus =\n | \"queued\"\n | \"ringing\"\n | \"in-progress\"\n | \"completed\"\n | \"busy\"\n | \"failed\"\n | \"no-answer\"\n | \"canceled\";\n\ntype ListCallsOptions = {\n to?: string;\n from?: string;\n status?: CallStatus;\n startTime?: string;\n limit?: number;\n};\n\nexport default tool({\n id: \"list-calls\",\n description:\n \"List recent phone calls from your Twilio account. Supports filtering by recipient, sender, status, and date.\",\n inputSchema: z.object({\n to: z\n .string()\n .optional()\n .describe(\"Filter by recipient phone number in E.164 format (e.g., +14155552671)\"),\n from: z\n .string()\n .optional()\n .describe(\"Filter by sender phone number in E.164 format (e.g., +14155552671)\"),\n status: z\n .enum([\n \"queued\",\n \"ringing\",\n \"in-progress\",\n \"completed\",\n \"busy\",\n \"failed\",\n \"no-answer\",\n \"canceled\",\n ])\n .optional()\n .describe(\"Filter by call status\"),\n startTime: z\n .string()\n .optional()\n .describe(\"Filter by start time in YYYY-MM-DD format (e.g., 2024-01-15)\"),\n limit: z\n .number()\n .min(1)\n .max(100)\n .optional()\n .describe(\"Maximum number of calls to return (default: 20, max: 100)\"),\n }),\n execute: async ({ to, from, status, startTime, limit }) => {\n try {\n const options: ListCallsOptions = {\n ...(to ? { to: formatPhoneNumber(to) } : {}),\n ...(from ? { from: formatPhoneNumber(from) } : {}),\n ...(status ? { status } : {}),\n ...(startTime ? { startTime } : {}),\n ...(limit ? { limit } : {}),\n };\n\n const calls = await listCalls(options);\n\n if (calls.length === 0) {\n return {\n success: true,\n count: 0,\n calls: [],\n message: \"No calls found matching the criteria.\",\n };\n }\n\n const formattedCalls = calls.map((call) => ({\n sid: call.sid,\n direction: call.direction,\n from: call.from,\n to: call.to,\n status: call.status,\n startTime: call.start_time,\n endTime: call.end_time,\n duration: call.duration ? `${call.duration} seconds` : null,\n dateCreated: call.date_created,\n dateUpdated: call.date_updated,\n price: call.price ? `${call.price} ${call.price_unit}` : null,\n answeredBy: call.answered_by,\n }));\n\n const totalDuration = calls.reduce(\n (sum, call) => sum + (call.duration ? parseInt(call.duration, 10) : 0),\n 0,\n );\n\n const statusCounts = calls.reduce<Record<string, number>>((acc, call) => {\n acc[call.status] = (acc[call.status] ?? 0) + 1;\n return acc;\n }, {});\n\n return {\n success: true,\n count: calls.length,\n calls: formattedCalls,\n statistics: {\n totalCalls: calls.length,\n totalDuration: `${totalDuration} seconds (${Math.round(totalDuration / 60)} minutes)`,\n statusBreakdown: statusCounts,\n },\n message: `Found ${calls.length} call${calls.length === 1 ? \"\" : \"s\"}.`,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"not configured\")) {\n return {\n error: \"Twilio not configured. Please set TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_PHONE_NUMBER.\",\n setupUrl: \"https://console.twilio.com/\",\n };\n }\n throw error;\n }\n },\n});\n",
|
|
695
|
+
".env.example": "# Twilio Integration\n# Get your credentials at https://console.twilio.com/\n# Trial account: Can only send to verified numbers, messages prefixed with trial notice\n# Production account: Remove all restrictions by upgrading at https://console.twilio.com/billing\n\n# Your Twilio Account SID (starts with AC)\nTWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# Your Twilio Auth Token (keep this secret!)\nTWILIO_AUTH_TOKEN=your_auth_token_here\n\n# Your Twilio phone number in E.164 format (e.g., +14155552671)\n# Get one at: https://console.twilio.com/us1/develop/phone-numbers/manage/search\nTWILIO_PHONE_NUMBER=+1234567890\n"
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
"integration:hubspot": {
|
|
699
|
+
"files": {
|
|
700
|
+
"lib/hubspot-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst HUBSPOT_BASE_URL = \"https://api.hubapi.com\";\n\ninterface HubSpotPagination {\n after?: string;\n next?: {\n after: string;\n link: string;\n };\n}\n\ninterface HubSpotResponse<T> {\n results: T[];\n paging?: HubSpotPagination;\n}\n\ninterface HubSpotContact {\n id: string;\n properties: {\n email?: string;\n firstname?: string;\n lastname?: string;\n phone?: string;\n company?: string;\n website?: string;\n jobtitle?: string;\n createdate?: string;\n lastmodifieddate?: string;\n [key: string]: string | undefined;\n };\n createdAt: string;\n updatedAt: string;\n archived: boolean;\n}\n\ninterface HubSpotCompany {\n id: string;\n properties: {\n name?: string;\n domain?: string;\n city?: string;\n state?: string;\n country?: string;\n industry?: string;\n phone?: string;\n createdate?: string;\n [key: string]: string | undefined;\n };\n createdAt: string;\n updatedAt: string;\n archived: boolean;\n}\n\ninterface HubSpotDeal {\n id: string;\n properties: {\n dealname?: string;\n amount?: string;\n dealstage?: string;\n pipeline?: string;\n closedate?: string;\n createdate?: string;\n [key: string]: string | undefined;\n };\n createdAt: string;\n updatedAt: string;\n archived: boolean;\n}\n\nfunction buildQueryString(options: {\n limit?: number;\n after?: string;\n properties?: string[];\n defaultProperties: string[];\n}): string {\n const params = new URLSearchParams();\n\n if (options.limit) params.set(\"limit\", options.limit.toString());\n if (options.after) params.set(\"after\", options.after);\n\n const properties =\n options.properties && options.properties.length > 0\n ? options.properties\n : options.defaultProperties;\n\n for (const prop of properties) params.append(\"properties\", prop);\n\n const queryString = params.toString();\n return queryString ? `?${queryString}` : \"\";\n}\n\nasync function hubspotFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with HubSpot. Please connect your account.\");\n }\n\n const response = await fetch(`${HUBSPOT_BASE_URL}${endpoint}`, {\n ...options,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({} as { message?: string }));\n throw new Error(\n `HubSpot API error: ${response.status} ${error.message || response.statusText}`,\n );\n }\n\n return response.json();\n}\n\n// ============================================================================\n// CONTACTS\n// ============================================================================\n\nexport function listContacts(options?: {\n limit?: number;\n after?: string;\n properties?: string[];\n}): Promise<HubSpotResponse<HubSpotContact>> {\n const query = buildQueryString({\n limit: options?.limit,\n after: options?.after,\n properties: options?.properties,\n defaultProperties: [\"email\", \"firstname\", \"lastname\", \"phone\", \"company\", \"jobtitle\"],\n });\n\n return hubspotFetch<HubSpotResponse<HubSpotContact>>(`/crm/v3/objects/contacts${query}`);\n}\n\nexport function getContact(contactId: string, properties?: string[]): Promise<HubSpotContact> {\n const query = buildQueryString({\n properties,\n defaultProperties: [\"email\", \"firstname\", \"lastname\", \"phone\", \"company\", \"jobtitle\", \"website\"],\n });\n\n return hubspotFetch<HubSpotContact>(`/crm/v3/objects/contacts/${contactId}${query}`);\n}\n\nexport function createContact(properties: {\n email: string;\n firstname?: string;\n lastname?: string;\n phone?: string;\n company?: string;\n website?: string;\n jobtitle?: string;\n [key: string]: string | undefined;\n}): Promise<HubSpotContact> {\n return hubspotFetch<HubSpotContact>(\"/crm/v3/objects/contacts\", {\n method: \"POST\",\n body: JSON.stringify({ properties }),\n });\n}\n\nexport function updateContact(\n contactId: string,\n properties: {\n email?: string;\n firstname?: string;\n lastname?: string;\n phone?: string;\n company?: string;\n website?: string;\n jobtitle?: string;\n [key: string]: string | undefined;\n },\n): Promise<HubSpotContact> {\n return hubspotFetch<HubSpotContact>(`/crm/v3/objects/contacts/${contactId}`, {\n method: \"PATCH\",\n body: JSON.stringify({ properties }),\n });\n}\n\nexport function searchContacts(options: {\n query?: string;\n filterGroups?: Array<{\n filters: Array<{\n propertyName: string;\n operator: string;\n value: string;\n }>;\n }>;\n properties?: string[];\n limit?: number;\n after?: string;\n}): Promise<HubSpotResponse<HubSpotContact>> {\n const body: Record<string, unknown> = {\n properties:\n options.properties && options.properties.length > 0\n ? options.properties\n : [\"email\", \"firstname\", \"lastname\", \"phone\", \"company\", \"jobtitle\"],\n };\n\n if (options.filterGroups) body.filterGroups = options.filterGroups;\n if (options.limit) body.limit = options.limit;\n if (options.after) body.after = options.after;\n\n return hubspotFetch<HubSpotResponse<HubSpotContact>>(\"/crm/v3/objects/contacts/search\", {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n}\n\n// ============================================================================\n// COMPANIES\n// ============================================================================\n\nexport function listCompanies(options?: {\n limit?: number;\n after?: string;\n properties?: string[];\n}): Promise<HubSpotResponse<HubSpotCompany>> {\n const query = buildQueryString({\n limit: options?.limit,\n after: options?.after,\n properties: options?.properties,\n defaultProperties: [\"name\", \"domain\", \"city\", \"state\", \"industry\", \"phone\"],\n });\n\n return hubspotFetch<HubSpotResponse<HubSpotCompany>>(`/crm/v3/objects/companies${query}`);\n}\n\nexport function getCompany(companyId: string, properties?: string[]): Promise<HubSpotCompany> {\n const query = buildQueryString({\n properties,\n defaultProperties: [\"name\", \"domain\", \"city\", \"state\", \"country\", \"industry\", \"phone\"],\n });\n\n return hubspotFetch<HubSpotCompany>(`/crm/v3/objects/companies/${companyId}${query}`);\n}\n\nexport function createCompany(properties: {\n name: string;\n domain?: string;\n city?: string;\n state?: string;\n country?: string;\n industry?: string;\n phone?: string;\n [key: string]: string | undefined;\n}): Promise<HubSpotCompany> {\n return hubspotFetch<HubSpotCompany>(\"/crm/v3/objects/companies\", {\n method: \"POST\",\n body: JSON.stringify({ properties }),\n });\n}\n\n// ============================================================================\n// DEALS\n// ============================================================================\n\nexport function listDeals(options?: {\n limit?: number;\n after?: string;\n properties?: string[];\n}): Promise<HubSpotResponse<HubSpotDeal>> {\n const query = buildQueryString({\n limit: options?.limit,\n after: options?.after,\n properties: options?.properties,\n defaultProperties: [\"dealname\", \"amount\", \"dealstage\", \"pipeline\", \"closedate\"],\n });\n\n return hubspotFetch<HubSpotResponse<HubSpotDeal>>(`/crm/v3/objects/deals${query}`);\n}\n\nexport function getDeal(dealId: string, properties?: string[]): Promise<HubSpotDeal> {\n const query = buildQueryString({\n properties,\n defaultProperties: [\"dealname\", \"amount\", \"dealstage\", \"pipeline\", \"closedate\"],\n });\n\n return hubspotFetch<HubSpotDeal>(`/crm/v3/objects/deals/${dealId}${query}`);\n}\n\nexport function createDeal(properties: {\n dealname: string;\n amount?: string;\n dealstage?: string;\n pipeline?: string;\n closedate?: string;\n [key: string]: string | undefined;\n}): Promise<HubSpotDeal> {\n return hubspotFetch<HubSpotDeal>(\"/crm/v3/objects/deals\", {\n method: \"POST\",\n body: JSON.stringify({ properties }),\n });\n}\n\nexport function updateDeal(\n dealId: string,\n properties: {\n dealname?: string;\n amount?: string;\n dealstage?: string;\n pipeline?: string;\n closedate?: string;\n [key: string]: string | undefined;\n },\n): Promise<HubSpotDeal> {\n return hubspotFetch<HubSpotDeal>(`/crm/v3/objects/deals/${dealId}`, {\n method: \"PATCH\",\n body: JSON.stringify({ properties }),\n });\n}\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\nexport function formatContactName(contact: HubSpotContact): string {\n const parts = [contact.properties.firstname, contact.properties.lastname].filter(\n (p): p is string => Boolean(p),\n );\n\n if (parts.length > 0) return parts.join(\" \");\n return contact.properties.email || \"Unnamed Contact\";\n}\n\nexport function formatCompanyName(company: HubSpotCompany): string {\n return company.properties.name || company.properties.domain || \"Unnamed Company\";\n}\n\nexport function formatDealName(deal: HubSpotDeal): string {\n return deal.properties.dealname || \"Unnamed Deal\";\n}\n\nexport type { HubSpotCompany, HubSpotContact, HubSpotDeal, HubSpotResponse };\n",
|
|
701
|
+
"tools/create-contact.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createContact, formatContactName } from \"../../lib/hubspot-client.ts\";\n\nexport default tool({\n id: \"create-contact\",\n description: \"Create a new contact in HubSpot CRM. Email is required, other fields are optional.\",\n inputSchema: z.object({\n email: z.string().email().describe(\"Contact email address (required)\"),\n firstname: z.string().optional().describe(\"First name\"),\n lastname: z.string().optional().describe(\"Last name\"),\n phone: z.string().optional().describe(\"Phone number\"),\n company: z.string().optional().describe(\"Company name\"),\n jobtitle: z.string().optional().describe(\"Job title\"),\n website: z.string().optional().describe(\"Website URL\"),\n }),\n async execute({ email, firstname, lastname, phone, company, jobtitle, website }) {\n const properties: Record<string, string> = {\n email,\n ...(firstname ? { firstname } : {}),\n ...(lastname ? { lastname } : {}),\n ...(phone ? { phone } : {}),\n ...(company ? { company } : {}),\n ...(jobtitle ? { jobtitle } : {}),\n ...(website ? { website } : {}),\n };\n\n const contact = await createContact(properties);\n const name = formatContactName(contact);\n\n return {\n id: contact.id,\n name,\n email: contact.properties.email,\n phone: contact.properties.phone,\n company: contact.properties.company,\n jobTitle: contact.properties.jobtitle,\n website: contact.properties.website,\n createdAt: contact.createdAt,\n message: `Successfully created contact: ${name}`,\n };\n },\n});\n",
|
|
702
|
+
"tools/list-deals.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatDealName, listDeals } from \"../../lib/hubspot-client.ts\";\n\nexport default tool({\n id: \"list-deals\",\n description:\n \"List sales deals from your HubSpot CRM. Returns deal information including name, amount, stage, and close date.\",\n inputSchema: z.object({\n limit: z.number().min(1).max(100).default(10).describe(\"Maximum number of deals to return\"),\n properties: z.array(z.string()).optional().describe(\"Additional properties to retrieve\"),\n }),\n async execute({ limit, properties }) {\n const response = await listDeals({ limit, properties });\n\n return {\n deals: response.results.map((deal) => {\n const additionalProperties = properties\n ? Object.fromEntries(\n properties\n .filter((prop) => deal.properties[prop] !== undefined)\n .map((prop) => [prop, deal.properties[prop]]),\n )\n : undefined;\n\n return {\n id: deal.id,\n name: formatDealName(deal),\n amount: deal.properties.amount,\n stage: deal.properties.dealstage,\n pipeline: deal.properties.pipeline,\n closeDate: deal.properties.closedate,\n createdAt: deal.createdAt,\n updatedAt: deal.updatedAt,\n additionalProperties,\n };\n }),\n hasMore: response.paging?.next != null,\n nextAfter: response.paging?.next?.after,\n };\n },\n});\n",
|
|
703
|
+
"tools/create-deal.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { createDeal, formatDealName } from \"../../lib/hubspot-client.ts\";\n\nexport default tool({\n id: \"create-deal\",\n description:\n \"Create a new deal in HubSpot CRM. Deal name is required, other fields are optional.\",\n inputSchema: z.object({\n dealname: z.string().describe(\"Deal name (required)\"),\n amount: z.string().optional().describe(\"Deal amount in the account currency\"),\n dealstage: z\n .string()\n .optional()\n .describe(\n 'Current stage of the deal (e.g., \"appointmentscheduled\", \"qualifiedtobuy\", \"closedwon\")',\n ),\n pipeline: z.string().optional().describe(\"Pipeline ID for the deal\"),\n closedate: z\n .string()\n .optional()\n .describe(\"Expected close date in format YYYY-MM-DD or timestamp\"),\n }),\n async execute({ dealname, amount, dealstage, pipeline, closedate }) {\n const properties: Record<string, string> = {\n dealname,\n ...(amount ? { amount } : {}),\n ...(dealstage ? { dealstage } : {}),\n ...(pipeline ? { pipeline } : {}),\n ...(closedate ? { closedate } : {}),\n };\n\n const deal = await createDeal(properties);\n const name = formatDealName(deal);\n\n return {\n id: deal.id,\n name,\n amount: deal.properties.amount,\n stage: deal.properties.dealstage,\n pipeline: deal.properties.pipeline,\n closeDate: deal.properties.closedate,\n createdAt: deal.createdAt,\n message: `Successfully created deal: ${name}`,\n };\n },\n});\n",
|
|
704
|
+
"tools/list-contacts.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatContactName, listContacts } from \"../../lib/hubspot-client.ts\";\n\nexport default tool({\n id: \"list-contacts\",\n description:\n \"List contacts from your HubSpot CRM. Returns contact information including name, email, phone, company, and job title.\",\n inputSchema: z.object({\n limit: z.number().min(1).max(100).default(10).describe(\"Maximum number of contacts to return\"),\n properties: z\n .array(z.string())\n .optional()\n .describe(\"Additional properties to retrieve (e.g., website, city, state)\"),\n }),\n async execute({ limit, properties }) {\n const response = await listContacts({ limit, properties });\n\n return {\n contacts: response.results.map((contact) => {\n const additionalProperties = properties\n ? Object.fromEntries(\n properties\n .filter((prop) => contact.properties[prop] !== undefined)\n .map((prop) => [prop, contact.properties[prop]]),\n )\n : undefined;\n\n return {\n id: contact.id,\n name: formatContactName(contact),\n email: contact.properties.email,\n phone: contact.properties.phone,\n company: contact.properties.company,\n jobTitle: contact.properties.jobtitle,\n createdAt: contact.createdAt,\n updatedAt: contact.updatedAt,\n additionalProperties,\n };\n }),\n hasMore: Boolean(response.paging?.next),\n nextAfter: response.paging?.next?.after,\n };\n },\n});\n",
|
|
705
|
+
"tools/get-contact.ts": "import { tool } from \"veryfront/tool\";\nimport { z } from \"zod\";\nimport { formatContactName, getContact } from \"../../lib/hubspot-client.ts\";\n\nexport default tool({\n id: \"get-contact\",\n description:\n \"Get detailed information about a specific contact in HubSpot CRM by their contact ID.\",\n inputSchema: z.object({\n contactId: z.string().describe(\"The HubSpot contact ID\"),\n properties: z\n .array(z.string())\n .optional()\n .describe(\n \"Additional properties to retrieve (e.g., website, city, state, notes)\",\n ),\n }),\n async execute({ contactId, properties }) {\n const contact = await getContact(contactId, properties);\n\n let additionalProperties: Record<string, unknown> | undefined;\n\n if (properties) {\n additionalProperties = Object.fromEntries(\n properties\n .filter((prop) => contact.properties[prop] !== undefined)\n .map((prop) => [prop, contact.properties[prop]]),\n );\n }\n\n return {\n id: contact.id,\n name: formatContactName(contact),\n email: contact.properties.email,\n phone: contact.properties.phone,\n company: contact.properties.company,\n jobTitle: contact.properties.jobtitle,\n website: contact.properties.website,\n createdAt: contact.createdAt,\n updatedAt: contact.updatedAt,\n archived: contact.archived,\n additionalProperties,\n allProperties: contact.properties,\n };\n },\n});\n",
|
|
706
|
+
"app/api/auth/hubspot/route.ts": "import { createOAuthInitHandler, hubspotConfig, memoryTokenStore } from \"veryfront/oauth\";\n\nexport const GET = createOAuthInitHandler(hubspotConfig, { tokenStore: memoryTokenStore });\n",
|
|
707
|
+
"app/api/auth/hubspot/callback/route.ts": "import { createOAuthCallbackHandler, hubspotConfig, memoryTokenStore } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\n\nconst USER_ID = \"current-user\";\n\nconst hybridTokenStore = {\n async getTokens(serviceId: string) {\n return tokenStore.getToken(USER_ID, serviceId);\n },\n async setTokens(\n serviceId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(USER_ID, serviceId, tokens);\n },\n async clearTokens(serviceId: string) {\n await tokenStore.revokeToken(USER_ID, serviceId);\n },\n getState: (state: string) => memoryTokenStore.getState(state),\n setState: (state: { state: string; codeVerifier?: string; createdAt: number }) => memoryTokenStore.setState(state),\n clearState: (state: string) => memoryTokenStore.clearState(state),\n};\n\nexport const GET = createOAuthCallbackHandler(hubspotConfig, { tokenStore: hybridTokenStore });\n",
|
|
708
|
+
".env.example": "# HubSpot OAuth Configuration\n# Get these from https://app.hubspot.com/developer\n\nHUBSPOT_CLIENT_ID=your_client_id_here\nHUBSPOT_CLIENT_SECRET=your_client_secret_here\n"
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
};
|