@skylandnpm/octopus-cli 0.0.1 → 0.0.2

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.
@@ -11,10 +11,11 @@ export function registerAttachmentsCommands(program) {
11
11
  .description("下载任务附件")
12
12
  .requiredOption("--project-no <number>", "项目编号", parseInt)
13
13
  .requiredOption("--attachment-id <id>", "附件ID")
14
+ .option("--email <email>", "用户邮箱(用于匹配对应 accessToken,多账号时建议指定)")
14
15
  .option("--output <path>", "下载保存路径")
15
16
  .action(async (opts) => {
16
17
  try {
17
- const client = getClient();
18
+ const client = getClient(opts.email);
18
19
  const res = await client.get(`/api/open/tasks/attachments/${opts.attachmentId}/download?projectNo=${opts.projectNo}`);
19
20
  if (res.data.success) {
20
21
  const { fileName, fileUrl } = res.data.data;
@@ -46,9 +47,10 @@ export function registerAttachmentsCommands(program) {
46
47
  .requiredOption("--log-id <logId>", "流水记录ID")
47
48
  .requiredOption("--project-no <number>", "项目编号", parseInt)
48
49
  .requiredOption("--file <path>", "附件文件路径")
50
+ .option("--email <email>", "用户邮箱(用于匹配对应 accessToken,多账号时建议指定)")
49
51
  .action(async (opts) => {
50
52
  try {
51
- const client = getClient();
53
+ const client = getClient(opts.email);
52
54
  const FormData = (await import("form-data")).default;
53
55
  const form = new FormData();
54
56
  form.append("projectNo", opts.projectNo);
@@ -76,9 +78,10 @@ export function registerAttachmentsCommands(program) {
76
78
  .description("获取流水记录附件列表")
77
79
  .requiredOption("--log-id <logId>", "流水记录ID")
78
80
  .requiredOption("--project-no <number>", "项目编号", parseInt)
81
+ .option("--email <email>", "用户邮箱(用于匹配对应 accessToken,多账号时建议指定)")
79
82
  .action(async (opts) => {
80
83
  try {
81
- const client = getClient();
84
+ const client = getClient(opts.email);
82
85
  const res = await client.post(`/api/open/task-logs/attachments/${opts.logId}/list`, { projectNo: opts.projectNo });
83
86
  if (res.data.success) {
84
87
  console.log(JSON.stringify(res.data.data, null, 2));
@@ -1,54 +1,149 @@
1
1
  import { loadConfig, saveConfig } from "../config.js";
2
- const KEY_MAP = {
3
- baseUrl: "OCTOPUS_BASE_URL",
4
- token: "OCTOPUS_ACCESS_TOKEN",
5
- };
6
2
  export function registerConfigCommands(program) {
7
3
  const configCmd = program.command("config").description("配置管理");
4
+ // ─── config get ───
8
5
  configCmd
9
6
  .command("get")
10
7
  .description("查看当前配置")
11
8
  .action(() => {
12
9
  const config = loadConfig();
13
- const baseUrl = config.env?.OCTOPUS_BASE_URL || "(未设置)";
14
- const token = config.env?.OCTOPUS_ACCESS_TOKEN
15
- ? "********" + config.env.OCTOPUS_ACCESS_TOKEN.slice(-10)
16
- : "(未设置)";
10
+ const baseUrl = config.baseUrl || config.env?.OCTOPUS_BASE_URL || "(未设置)";
17
11
  console.log(`baseUrl: ${baseUrl}`);
18
- console.log(`token: ${token}`);
12
+ if (config.accounts && config.accounts.length > 0) {
13
+ console.log(`\n账号列表 (共 ${config.accounts.length} 个):`);
14
+ for (const acc of config.accounts) {
15
+ const isDefault = acc.email === config.defaultAccount ? " (默认)" : "";
16
+ const masked = "********" + acc.accessToken.slice(-10);
17
+ console.log(` - ${acc.email}${isDefault} token: ${masked}`);
18
+ }
19
+ }
20
+ else {
21
+ const token = config.env?.OCTOPUS_ACCESS_TOKEN
22
+ ? "********" + config.env.OCTOPUS_ACCESS_TOKEN.slice(-10)
23
+ : "(未设置)";
24
+ console.log(`token (旧版全局): ${token}`);
25
+ }
19
26
  });
27
+ // ─── config set ───
20
28
  configCmd
21
29
  .command("set <key> <value>")
22
- .description("设置配置项 (baseUrl | token)")
30
+ .description("设置配置项 (baseUrl)")
23
31
  .action((key, value) => {
24
- if (key !== "baseUrl" && key !== "token") {
25
- console.log("仅支持设置 baseUrl token");
32
+ if (key !== "baseUrl") {
33
+ console.log("仅支持设置 baseUrl,账号管理请使用 octopus account 命令");
26
34
  return;
27
35
  }
28
36
  const config = loadConfig();
37
+ config.baseUrl = value.replace(/\/+$/, "");
38
+ // 同步旧版字段
29
39
  config.env = config.env || {};
30
- const finalValue = key === "baseUrl" ? value.replace(/\/+$/, "") : value;
31
- config.env[KEY_MAP[key]] =
32
- finalValue;
40
+ config.env.OCTOPUS_BASE_URL = config.baseUrl;
33
41
  saveConfig(config);
34
42
  console.log(`已设置 ${key},保存到 ~/.octopus/settings.json`);
35
43
  });
44
+ // ─── config delete ───
36
45
  configCmd
37
46
  .command("delete <key>")
38
- .description("删除配置项 (baseUrl | token)")
47
+ .description("删除配置项 (baseUrl)")
39
48
  .action((key) => {
40
- if (key !== "baseUrl" && key !== "token") {
41
- console.log("仅支持删除 baseUrl token");
49
+ if (key !== "baseUrl") {
50
+ console.log("仅支持删除 baseUrl,账号管理请使用 octopus account 命令");
42
51
  return;
43
52
  }
44
53
  const config = loadConfig();
45
- const envKey = KEY_MAP[key];
46
- if (!config.env?.[envKey]) {
47
- console.log(`配置项 ${key} 不存在`);
54
+ delete config.baseUrl;
55
+ if (config.env)
56
+ delete config.env.OCTOPUS_BASE_URL;
57
+ saveConfig(config);
58
+ console.log(`已删除配置项 ${key},保存到 ~/.octopus/settings.json`);
59
+ });
60
+ // ═══════════════════════════════════════
61
+ // account 子命令组
62
+ // ═══════════════════════════════════════
63
+ const accountCmd = program.command("account").description("多账号管理");
64
+ // ─── account add <email> <accessToken> ───
65
+ accountCmd
66
+ .command("add <email> <accessToken>")
67
+ .description("添加账号")
68
+ .action((email, accessToken) => {
69
+ const config = loadConfig();
70
+ config.accounts = config.accounts || [];
71
+ const existing = config.accounts.find((a) => a.email === email);
72
+ if (existing) {
73
+ existing.accessToken = accessToken;
74
+ console.log(`已更新账号 ${email} 的 token`);
75
+ }
76
+ else {
77
+ config.accounts.push({ email, accessToken });
78
+ console.log(`已添加账号 ${email}`);
79
+ }
80
+ // 如果只有一个账号,自动设为默认
81
+ if (config.accounts.length === 1) {
82
+ config.defaultAccount = email;
83
+ }
84
+ saveConfig(config);
85
+ });
86
+ // ─── account list ───
87
+ accountCmd
88
+ .command("list")
89
+ .description("查看所有已配置的账号")
90
+ .action(() => {
91
+ const config = loadConfig();
92
+ if (!config.accounts || config.accounts.length === 0) {
93
+ console.log("暂无账号配置,请使用 octopus account add <email> <accessToken> 添加");
48
94
  return;
49
95
  }
50
- delete config.env[envKey];
96
+ console.log(`已配置 ${config.accounts.length} 个账号:\n`);
97
+ for (const acc of config.accounts) {
98
+ const isDefault = acc.email === config.defaultAccount ? " ★ 默认" : "";
99
+ const masked = "********" + acc.accessToken.slice(-10);
100
+ console.log(` ${acc.email}${isDefault}`);
101
+ console.log(` token: ${masked}\n`);
102
+ }
103
+ });
104
+ // ─── account remove <email> ───
105
+ accountCmd
106
+ .command("remove <email>")
107
+ .description("移除指定账号")
108
+ .action((email) => {
109
+ const config = loadConfig();
110
+ if (!config.accounts || config.accounts.length === 0) {
111
+ console.log("暂无账号配置");
112
+ return;
113
+ }
114
+ const idx = config.accounts.findIndex((a) => a.email === email);
115
+ if (idx === -1) {
116
+ console.log(`未找到账号 ${email}`);
117
+ return;
118
+ }
119
+ config.accounts.splice(idx, 1);
120
+ console.log(`已移除账号 ${email}`);
121
+ // 如果移除的是默认账号,自动切换到第一个
122
+ if (config.defaultAccount === email) {
123
+ config.defaultAccount = config.accounts.length > 0 ? config.accounts[0].email : undefined;
124
+ if (config.defaultAccount) {
125
+ console.log(`默认账号已切换为 ${config.defaultAccount}`);
126
+ }
127
+ }
51
128
  saveConfig(config);
52
- console.log(`已删除配置项 ${key},保存到 ~/.octopus/settings.json`);
129
+ });
130
+ // ─── account set-default <email> ───
131
+ accountCmd
132
+ .command("set-default <email>")
133
+ .description("设置默认账号")
134
+ .action((email) => {
135
+ const config = loadConfig();
136
+ if (!config.accounts || config.accounts.length === 0) {
137
+ console.log("暂无账号配置,请先使用 octopus account add 添加");
138
+ return;
139
+ }
140
+ const acc = config.accounts.find((a) => a.email === email);
141
+ if (!acc) {
142
+ console.log(`未找到账号 ${email},请先使用 octopus account add 添加`);
143
+ return;
144
+ }
145
+ config.defaultAccount = email;
146
+ saveConfig(config);
147
+ console.log(`已将 ${email} 设为默认账号`);
53
148
  });
54
149
  }
@@ -10,7 +10,7 @@ export function registerTasksCommands(program) {
10
10
  .option("--status <status>", "任务状态筛选: not_started, in_progress, completed, error, closed")
11
11
  .action(async (opts) => {
12
12
  try {
13
- const client = getClient();
13
+ const client = getClient(opts.email);
14
14
  const res = await client.post("/api/open/tasks/list", {
15
15
  projectNo: opts.projectNo,
16
16
  email: opts.email,
@@ -33,9 +33,10 @@ export function registerTasksCommands(program) {
33
33
  .description("获取任务详情")
34
34
  .requiredOption("--project-no <number>", "项目编号", parseInt)
35
35
  .requiredOption("--task-no <number>", "任务编号", parseInt)
36
+ .option("--email <email>", "用户邮箱(用于匹配对应 accessToken,多账号时建议指定)")
36
37
  .action(async (opts) => {
37
38
  try {
38
- const client = getClient();
39
+ const client = getClient(opts.email);
39
40
  const res = await client.post("/api/open/tasks/detail", {
40
41
  projectNo: opts.projectNo,
41
42
  taskNo: opts.taskNo,
@@ -61,7 +62,7 @@ export function registerTasksCommands(program) {
61
62
  .option("--remark <remark>", "备注信息")
62
63
  .action(async (opts) => {
63
64
  try {
64
- const client = getClient();
65
+ const client = getClient(opts.email);
65
66
  const res = await client.post("/api/open/tasks/approve", {
66
67
  projectNo: opts.projectNo,
67
68
  taskNo: opts.taskNo,
@@ -90,7 +91,7 @@ export function registerTasksCommands(program) {
90
91
  .option("--remark <remark>", "备注信息")
91
92
  .action(async (opts) => {
92
93
  try {
93
- const client = getClient();
94
+ const client = getClient(opts.email);
94
95
  const res = await client.post("/api/open/tasks/start", {
95
96
  projectNo: opts.projectNo,
96
97
  taskNo: opts.taskNo,
@@ -120,7 +121,7 @@ export function registerTasksCommands(program) {
120
121
  .option("--hours-spent <minutes>", "本次消耗工时(分钟)", parseInt)
121
122
  .action(async (opts) => {
122
123
  try {
123
- const client = getClient();
124
+ const client = getClient(opts.email);
124
125
  const res = await client.post("/api/open/tasks/complete", {
125
126
  projectNo: opts.projectNo,
126
127
  taskNo: opts.taskNo,
@@ -150,7 +151,7 @@ export function registerTasksCommands(program) {
150
151
  .requiredOption("--remark <remark>", "异常原因")
151
152
  .action(async (opts) => {
152
153
  try {
153
- const client = getClient();
154
+ const client = getClient(opts.email);
154
155
  const res = await client.post("/api/open/tasks/error", {
155
156
  projectNo: opts.projectNo,
156
157
  taskNo: opts.taskNo,
@@ -179,7 +180,7 @@ export function registerTasksCommands(program) {
179
180
  .requiredOption("--remark <remark>", "备注内容")
180
181
  .action(async (opts) => {
181
182
  try {
182
- const client = getClient();
183
+ const client = getClient(opts.email);
183
184
  const res = await client.post("/api/open/tasks/comments", {
184
185
  projectNo: opts.projectNo,
185
186
  taskNo: opts.taskNo,
@@ -204,9 +205,10 @@ export function registerTasksCommands(program) {
204
205
  .description("获取任务附件列表")
205
206
  .requiredOption("--project-no <number>", "项目编号", parseInt)
206
207
  .requiredOption("--task-no <number>", "任务编号", parseInt)
208
+ .option("--email <email>", "用户邮箱(用于匹配对应 accessToken,多账号时建议指定)")
207
209
  .action(async (opts) => {
208
210
  try {
209
- const client = getClient();
211
+ const client = getClient(opts.email);
210
212
  const res = await client.post("/api/open/tasks/attachments", {
211
213
  projectNo: opts.projectNo,
212
214
  taskNo: opts.taskNo,
@@ -9,7 +9,7 @@ export function registerTodosCommands(program) {
9
9
  .requiredOption("--email <email>", "用户邮箱")
10
10
  .action(async (opts) => {
11
11
  try {
12
- const client = getClient();
12
+ const client = getClient(opts.email);
13
13
  const res = await client.post("/api/open/todos/pending", {
14
14
  projectNo: opts.projectNo,
15
15
  email: opts.email,
@@ -34,7 +34,7 @@ export function registerTodosCommands(program) {
34
34
  .option("--status <status>", "待办状态筛选: pending, approved, completed, failed, suspended, closed")
35
35
  .action(async (opts) => {
36
36
  try {
37
- const client = getClient();
37
+ const client = getClient(opts.email);
38
38
  const res = await client.post("/api/open/todos/approved", {
39
39
  projectNo: opts.projectNo,
40
40
  email: opts.email,
@@ -62,7 +62,7 @@ export function registerTodosCommands(program) {
62
62
  .option("--description <description>", "待办描述")
63
63
  .action(async (opts) => {
64
64
  try {
65
- const client = getClient();
65
+ const client = getClient(opts.email);
66
66
  const res = await client.post("/api/open/todos/create", {
67
67
  projectNo: opts.projectNo,
68
68
  taskNo: opts.taskNo,
@@ -95,7 +95,7 @@ export function registerTodosCommands(program) {
95
95
  .option("--tokens-consumed <count>", "消耗Token数量", parseInt)
96
96
  .action(async (opts) => {
97
97
  try {
98
- const client = getClient();
98
+ const client = getClient(opts.email);
99
99
  const res = await client.post("/api/open/todos/feedback", {
100
100
  projectNo: opts.projectNo,
101
101
  todoId: opts.todoId,
package/dist/config.js CHANGED
@@ -19,14 +19,55 @@ export function saveConfig(config) {
19
19
  mkdirSync(CONFIG_DIR, { recursive: true });
20
20
  writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
21
21
  }
22
- export function getClient() {
22
+ /** 获取 baseUrl,优先从顶层 baseUrl,其次从旧版 env */
23
+ export function getBaseUrl() {
23
24
  const config = loadConfig();
24
- const baseUrl = config.env?.OCTOPUS_BASE_URL || process.env.OCTOPUS_BASE_URL;
25
- const token = config.env?.OCTOPUS_ACCESS_TOKEN || process.env.OCTOPUS_ACCESS_TOKEN;
25
+ return config.baseUrl || config.env?.OCTOPUS_BASE_URL || process.env.OCTOPUS_BASE_URL;
26
+ }
27
+ /**
28
+ * 根据 email 查找对应账号的 accessToken。
29
+ * 如果不传 email,则使用 defaultAccount 或第一个账号。
30
+ */
31
+ export function resolveToken(email) {
32
+ const config = loadConfig();
33
+ // 新版:从 accounts 中查找
34
+ if (config.accounts && config.accounts.length > 0) {
35
+ let account;
36
+ if (email) {
37
+ account = config.accounts.find((a) => a.email === email);
38
+ if (!account) {
39
+ throw new Error(`未找到邮箱为 ${email} 的账号,请先通过 octopus account add 添加`);
40
+ }
41
+ }
42
+ else {
43
+ // 使用默认账号
44
+ const defaultEmail = config.defaultAccount || config.accounts[0].email;
45
+ account = config.accounts.find((a) => a.email === defaultEmail);
46
+ if (!account) {
47
+ account = config.accounts[0];
48
+ }
49
+ }
50
+ return { token: account.accessToken, resolvedEmail: account.email };
51
+ }
52
+ // 兼容旧版:从全局 token 获取
53
+ const globalToken = config.env?.OCTOPUS_ACCESS_TOKEN || process.env.OCTOPUS_ACCESS_TOKEN;
54
+ if (globalToken) {
55
+ if (!email) {
56
+ throw new Error("旧版全局 token 模式下需要指定 --email 参数,或请迁移到多账号模式 (octopus account add)");
57
+ }
58
+ return { token: globalToken, resolvedEmail: email };
59
+ }
60
+ throw new Error("未配置任何账号,请先运行 octopus account add <email> <accessToken>");
61
+ }
62
+ /**
63
+ * 获取 HTTP 客户端。
64
+ * 可选传入 email 参数,用于从 accounts 中匹配对应的 accessToken。
65
+ */
66
+ export function getClient(email) {
67
+ const baseUrl = getBaseUrl();
26
68
  if (!baseUrl)
27
69
  throw new Error("缺少 baseUrl,请先运行 octopus config set baseUrl <url>");
28
- if (!token)
29
- throw new Error("缺少 token,请先运行 octopus config set token <token>");
70
+ const { token } = resolveToken(email);
30
71
  return axios.create({
31
72
  baseURL: baseUrl.replace(/\/+$/, ""),
32
73
  headers: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skylandnpm/octopus-cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "octopus": "./bin/octopus.js"