index.mjs 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. /**
  2. * __SKILL_NAME__ · ESM 入口
  3. * ---------------------------------------------------------------------------
  4. * 这里是技能的 SDK 端。所有公共能力都从本文件 export。
  5. * 纪律:ESM only、零依赖优先、不在此文件读文件系统(除非确有需要)。
  6. *
  7. * @example
  8. * import { greet, VERSION } from '__SKILL_NAME__';
  9. * console.log(greet('world'));
  10. */
  11. import fs from 'node:fs';
  12. import path from 'node:path';
  13. import os from 'node:os';
  14. export const VERSION = '0.1.0';
  15. export const SKILL_NAME = '__SKILL_NAME__';
  16. /** Fmode 平台基址(如需调用平台服务,从这里取) */
  17. export const FMODE_API_BASE = 'https://api.fmode.cn';
  18. export const FMODE_GATEWAY_BASE = 'https://server.fmode.cn';
  19. /**
  20. * 示例函数 —— 替换成你技能的真实能力。
  21. * @param {string} name
  22. * @returns {string}
  23. */
  24. export function greet(name = 'world') {
  25. return `Hello, ${name}! 来自 __SKILL_NAME__ v${VERSION}`;
  26. }
  27. /**
  28. * 示例:解析 Fmode 凭据(5 级链的精简版)。
  29. * 生产实现请直接复用 skill-core-guide 的 lib/bootstrap.mjs。
  30. *
  31. * @param {{cwd?: string}} [opts]
  32. * @returns {Promise<{token: string, source: string}|null>}
  33. */
  34. export async function resolveToken(opts = {}) {
  35. // 第 1 级:环境变量
  36. if (process.env.FMODE_API_TOKEN) {
  37. return { token: process.env.FMODE_API_TOKEN, source: 'env:FMODE_API_TOKEN' };
  38. }
  39. // 第 0 级:sessionToken 自举
  40. const sessionToken =
  41. process.env.FMODE_SESSION_TOKEN || readConfigToken(opts.cwd);
  42. if (sessionToken) {
  43. try {
  44. const res = await fetch(`${FMODE_GATEWAY_BASE}/api/fmode/voc-skill/install-prompt`, {
  45. method: 'POST',
  46. headers: { 'Content-Type': 'application/json', 'x-parse-session-token': sessionToken },
  47. body: JSON.stringify({ channel: 'claude-code', scope: 'user' }),
  48. signal: AbortSignal.timeout(15000),
  49. });
  50. if (res.ok) {
  51. const body = await res.json().catch(() => null);
  52. const prompt = body?.data?.prompt || '';
  53. const m = prompt.match(/sk-(?!ant-)[A-Za-z0-9_-]{8,}/);
  54. // ⚠️ token 仅内存持有,不落盘不进日志
  55. if (m) return { token: m[0], source: 'sessionToken 自举' };
  56. }
  57. } catch {
  58. /* 回落 */
  59. }
  60. }
  61. return null;
  62. }
  63. /** 从 ~/.fmode/config.json 读 sessionToken(ESM:用顶层 import,不用 require) */
  64. function readConfigToken(cwd) {
  65. try {
  66. const p = path.join(os.homedir(), '.fmode', 'config.json');
  67. if (!fs.existsSync(p)) return null;
  68. const cfg = JSON.parse(fs.readFileSync(p, 'utf-8').replace(/^/, ''));
  69. return cfg.sessionToken || (cfg.user && cfg.user.sessionToken) || null;
  70. } catch {
  71. return null;
  72. }
  73. }
  74. export default { VERSION, SKILL_NAME, greet, resolveToken };