agim-cli 1.1.4 → 1.1.5

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/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.1.5] - 2026-05-15
8
+
9
+ ### Fixed — Web console header parity + complete i18n
10
+
11
+ - **Header parity for Tasks / Reminders / Memos.** All three pages
12
+ now share the same flex header (gap:16px, h1 flex:1, uniform
13
+ border-button for nav links / theme toggle / language select).
14
+ Removed the legacy `<nav>` wrapper from Reminders + Memos so the
15
+ DOM matches Tasks; the only intended difference per page is the
16
+ self-referencing nav link being omitted.
17
+ - **Emoji prefix unified.** Page titles consolidated through
18
+ `T.h1` (`🗂 任务与定时` / `🔔 提醒` / `📋 备忘`) instead of
19
+ some pages baking the emoji into HTML and others omitting it.
20
+ Nav-link `↩ Chat` / `↩ 对话` prefix moved into `T.backToChat`.
21
+ - **Complete i18n migration of JS template strings.** Hardcoded
22
+ English strings in Reminders + Memos JS (loading / empty /
23
+ snooze / cancel / confirm dialogs / delete / map links / toast
24
+ text) were missed in the first pass and showed up as mixed
25
+ zh/en when the language was set to 中文. All ~19 strings now
26
+ resolve through `T.*` with en + zh parity.
27
+ - **Pre-1.0 schema rows surfacing Invalid Date** in older deploys —
28
+ belt-and-suspenders fix already in 1.1.4 carried forward.
29
+
7
30
  ## [1.1.4] - 2026-05-15
8
31
 
9
32
  ### Added — Web console catches up with task-recovery + A2A
@@ -14,20 +14,34 @@
14
14
  document.documentElement.lang = window.__lang === 'zh' ? 'zh-CN' : 'en';
15
15
  const T = {
16
16
  en: {
17
- title: 'Agim — Memos', h1: 'Memos',
18
- backToChat: 'Chat', toTasks: 'Tasks', toReminders: 'Reminders', toSettings: 'Settings',
17
+ title: 'Agim — Memos', h1: '📋 Memos',
18
+ backToChat: 'Chat', toTasks: 'Tasks', toReminders: 'Reminders', toSettings: 'Settings',
19
19
  filterAll: 'All', filterGeo: 'With location', filterExpired: 'Expired',
20
20
  searchPlaceholder: 'search (what / memo / who / address …)',
21
- empty: 'No memos matching this filter.',
21
+ loading: 'Loading…',
22
+ loadFailed: 'Load failed: {err}',
23
+ emptyNoMatch: 'No memos match your search.',
24
+ emptyNone: 'No memos yet. Just tell an agent in IM "记下…" / "remember that…" and they will save it.',
22
25
  delete: 'Delete', edit: 'Edit',
26
+ confirmDelete: 'Really delete memo #{id}?',
27
+ deleted: 'Deleted #{id}',
28
+ deleteFailed: 'Delete failed: {err}',
29
+ mapBaidu: 'Baidu Map', mapAmap: 'Amap', mapGoogle: 'Google Maps',
23
30
  },
24
31
  zh: {
25
- title: 'Agim — 备忘', h1: '备忘',
26
- backToChat: '对话', toTasks: '任务', toReminders: '提醒', toSettings: '设置',
32
+ title: 'Agim — 备忘', h1: '📋 备忘',
33
+ backToChat: '对话', toTasks: '任务', toReminders: '提醒', toSettings: '设置',
27
34
  filterAll: '全部', filterGeo: '有位置', filterExpired: '已过期',
28
35
  searchPlaceholder: '搜索内容(what / memo / who / 地址 …)',
29
- empty: '当前过滤下暂无备忘。',
36
+ loading: '加载中…',
37
+ loadFailed: '加载失败:{err}',
38
+ emptyNoMatch: '没匹配的 memo',
39
+ emptyNone: '还没记下任何 memo。在 IM 里说「记下…」让 agent 帮你存。',
30
40
  delete: '删除', edit: '编辑',
41
+ confirmDelete: '确定删除 memo #{id}?',
42
+ deleted: '已删除 #{id}',
43
+ deleteFailed: '删除失败:{err}',
44
+ mapBaidu: '百度地图', mapAmap: '高德地图', mapGoogle: 'Google',
31
45
  },
32
46
  };
33
47
  window.__t = T[window.__lang];
@@ -49,16 +63,29 @@
49
63
  font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", sans-serif;
50
64
  background: var(--bg); color: var(--fg); margin: 0; padding: 0;
51
65
  }
66
+ /* Header — kept in sync with tasks.html / reminders.html so all
67
+ dashboard pages share the same layout. */
52
68
  header {
53
- display: flex; align-items: center; justify-content: space-between;
54
- padding: 14px 20px; border-bottom: 1px solid var(--border); background: var(--card);
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 16px;
72
+ padding: 14px 24px;
73
+ border-bottom: 1px solid var(--border);
74
+ background: var(--card);
55
75
  }
56
- header h1 { margin: 0; font-size: 18px; font-weight: 600; }
57
- header nav a {
58
- color: var(--muted); text-decoration: none; padding: 4px 10px;
59
- border-radius: 4px; font-size: 13px; margin-left: 4px;
76
+ header h1 { margin: 0; font-size: 18px; font-weight: 600; flex: 1; }
77
+ header a, header button, header select {
78
+ color: var(--accent);
79
+ text-decoration: none;
80
+ font-size: 14px;
81
+ background: none;
82
+ border: 1px solid var(--border);
83
+ padding: 6px 12px;
84
+ border-radius: 4px;
85
+ cursor: pointer;
60
86
  }
61
- header nav a:hover { background: var(--bg); color: var(--fg); }
87
+ header select { color: var(--fg); }
88
+ header a:hover, header button:hover { border-color: var(--accent); }
62
89
  main { padding: 20px; max-width: 980px; margin: 0 auto; }
63
90
  .toolbar {
64
91
  display: flex; gap: 8px; margin-bottom: 18px; flex-wrap: wrap;
@@ -132,18 +159,16 @@
132
159
  </head>
133
160
  <body>
134
161
  <header>
135
- <h1>📋 <span id="page-title"></span></h1>
162
+ <h1 id="page-title"></h1>
136
163
  <button id="theme-toggle" type="button" aria-label="Toggle color theme"></button>
137
- <select id="langSelect" title="Language / 语言" style="margin-right:8px">
164
+ <select id="langSelect" title="Language / 语言">
138
165
  <option value="en">EN</option>
139
166
  <option value="zh">中文</option>
140
167
  </select>
141
- <nav>
142
- <a href="/" id="lnk-chat"></a>
143
- <a href="/tasks" id="lnk-tasks"></a>
144
- <a href="/reminders" id="lnk-reminders"></a>
145
- <a href="/settings" id="lnk-settings"></a>
146
- </nav>
168
+ <a href="/" id="lnk-chat"></a>
169
+ <a href="/tasks" id="lnk-tasks"></a>
170
+ <a href="/reminders" id="lnk-reminders"></a>
171
+ <a href="/settings" id="lnk-settings"></a>
147
172
  </header>
148
173
  <main>
149
174
  <div class="toolbar">
@@ -222,7 +247,7 @@
222
247
  }
223
248
 
224
249
  async function load() {
225
- $list.innerHTML = '<div class="empty">加载中…</div>'
250
+ $list.innerHTML = `<div class="empty">${T.loading}</div>`
226
251
  try {
227
252
  const params = new URLSearchParams()
228
253
  if (queryStr) params.set('query', queryStr)
@@ -237,16 +262,16 @@
237
262
  items = items.filter(m => m.expiresAt && new Date(m.expiresAt.replace(' ', 'T') + '+08:00') < now)
238
263
  }
239
264
  render(items)
240
- $stats.textContent = items.length ? `${items.length} 条` : ''
265
+ $stats.textContent = items.length ? (window.__lang === 'zh' ? `${items.length} 条` : `${items.length} memos`) : ''
241
266
  } catch (err) {
242
267
  $list.innerHTML = ''
243
- toast(`加载失败:${err.message}`, true)
268
+ toast(T.loadFailed.replace('{err}', err.message), true)
244
269
  }
245
270
  }
246
271
 
247
272
  function render(items) {
248
273
  if (!items.length) {
249
- $list.innerHTML = `<div class="empty">${queryStr ? '没匹配的 memo' : '还没记下任何 memo。在 IM 里说「记下…」让 agent 帮你存。'}</div>`
274
+ $list.innerHTML = `<div class="empty">${queryStr ? T.emptyNoMatch : T.emptyNone}</div>`
250
275
  return
251
276
  }
252
277
  $list.innerHTML = items.map(m => {
@@ -269,16 +294,16 @@
269
294
  let maps = ''
270
295
  if (m.mapUrls) {
271
296
  maps = `<div class="maps">
272
- <a href="${m.mapUrls.baidu}" target="_blank">百度地图</a>
273
- <a href="${m.mapUrls.amap}" target="_blank">高德地图</a>
274
- <a href="${m.mapUrls.google}" target="_blank">Google</a>
297
+ <a href="${m.mapUrls.baidu}" target="_blank">${T.mapBaidu}</a>
298
+ <a href="${m.mapUrls.amap}" target="_blank">${T.mapAmap}</a>
299
+ <a href="${m.mapUrls.google}" target="_blank">${T.mapGoogle}</a>
275
300
  </div>`
276
301
  }
277
302
 
278
303
  return `<div class="memo" data-id="${m.id}">
279
304
  <div class="body">${what}${original}${meta}${maps}</div>
280
305
  <div class="actions">
281
- <button type="button" class="btn danger" data-action="delete">删除</button>
306
+ <button type="button" class="btn danger" data-action="delete">${T.delete}</button>
282
307
  </div>
283
308
  </div>`
284
309
  }).join('')
@@ -292,12 +317,12 @@
292
317
  const id = card.dataset.id
293
318
  const action = btn.dataset.action
294
319
  if (action === 'delete') {
295
- if (!confirm(`确定删除 memo #${id}?`)) return
320
+ if (!confirm(T.confirmDelete.replace('{id}', id))) return
296
321
  try {
297
322
  await window.imhub.api(`/api/memos/${id}`, { method: 'DELETE' })
298
- toast(`已删除 #${id}`)
323
+ toast(T.deleted.replace('{id}', id))
299
324
  load()
300
- } catch (err) { toast(`删除失败:${err.message}`, true) }
325
+ } catch (err) { toast(T.deleteFailed.replace('{err}', err.message), true) }
301
326
  }
302
327
  })
303
328
 
@@ -15,22 +15,32 @@
15
15
  document.documentElement.lang = window.__lang === 'zh' ? 'zh-CN' : 'en';
16
16
  const T = {
17
17
  en: {
18
- title: 'Agim — Reminders', h1: 'Reminders',
19
- backToChat: 'Chat', toTasks: 'Tasks', toMemos: 'Memos', toSettings: 'Settings',
18
+ title: 'Agim — Reminders', h1: '🔔 Reminders',
19
+ backToChat: 'Chat', toTasks: 'Tasks', toMemos: 'Memos', toSettings: 'Settings',
20
20
  statusPending: 'Pending', statusFired: 'Fired', statusCancelled: 'Cancelled', statusFailed: 'Failed',
21
- empty: 'No reminders matching this filter.',
22
- searchPlaceholder: 'search…',
23
- cancel: 'Cancel', snooze: 'Snooze', delete: 'Delete',
21
+ loading: 'Loading…',
22
+ loadFailed: 'Load failed: {err}',
23
+ emptyStatus: 'No {status} reminders.',
24
+ cancel: 'Cancel', snooze: 'Snooze +5min', delete: 'Delete',
24
25
  fireAt: 'Fires at', recur: 'Repeat',
26
+ literalText: 'literal',
27
+ confirmCancel: 'Cancel reminder #{id}? (Recurring reminders will stop the whole loop.)',
28
+ cancelled: '✅ Cancelled #{id}',
29
+ snoozed: '⏰ Snoozed #{id} +5min',
25
30
  },
26
31
  zh: {
27
- title: 'Agim — 提醒', h1: '提醒',
28
- backToChat: '对话', toTasks: '任务', toMemos: '备忘', toSettings: '设置',
32
+ title: 'Agim — 提醒', h1: '🔔 提醒',
33
+ backToChat: '对话', toTasks: '任务', toMemos: '备忘', toSettings: '设置',
29
34
  statusPending: '待发', statusFired: '已发', statusCancelled: '已取消', statusFailed: '失败',
30
- empty: '当前过滤下暂无提醒。',
31
- searchPlaceholder: '搜索…',
32
- cancel: '取消', snooze: '推迟', delete: '删除',
35
+ loading: '加载中…',
36
+ loadFailed: '加载失败:{err}',
37
+ emptyStatus: '没有{status}的提醒',
38
+ cancel: '取消', snooze: '延 5 分钟', delete: '删除',
33
39
  fireAt: '触发时间', recur: '重复',
40
+ literalText: '字面文本',
41
+ confirmCancel: '取消提醒 #{id}?(循环提醒会终止整条循环)',
42
+ cancelled: '✅ 已取消 #{id}',
43
+ snoozed: '⏰ 已延后 #{id} 5 分钟',
34
44
  },
35
45
  };
36
46
  window.__t = T[window.__lang];
@@ -53,17 +63,30 @@
53
63
  background: var(--bg); color: var(--fg);
54
64
  margin: 0; padding: 0;
55
65
  }
66
+ /* Header — kept in sync with tasks.html so all dashboard pages share
67
+ the same layout. Flex with h1 flex:1 to push toggles + nav to the
68
+ right; uniform border-button look across <a> and <button>. */
56
69
  header {
57
- display: flex; align-items: center; justify-content: space-between;
58
- padding: 14px 20px; border-bottom: 1px solid var(--border);
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 16px;
73
+ padding: 14px 24px;
74
+ border-bottom: 1px solid var(--border);
59
75
  background: var(--card);
60
76
  }
61
- header h1 { margin: 0; font-size: 18px; font-weight: 600; }
62
- header nav a {
63
- margin-left: 12px; color: var(--muted); text-decoration: none;
64
- font-size: 13px;
77
+ header h1 { margin: 0; font-size: 18px; font-weight: 600; flex: 1; }
78
+ header a, header button, header select {
79
+ color: var(--accent);
80
+ text-decoration: none;
81
+ font-size: 14px;
82
+ background: none;
83
+ border: 1px solid var(--border);
84
+ padding: 6px 12px;
85
+ border-radius: 4px;
86
+ cursor: pointer;
65
87
  }
66
- header nav a:hover { color: var(--accent); }
88
+ header select { color: var(--fg); }
89
+ header a:hover, header button:hover { border-color: var(--accent); }
67
90
  main { max-width: 880px; margin: 24px auto; padding: 0 16px; }
68
91
  .filters {
69
92
  display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap;
@@ -118,18 +141,16 @@
118
141
  </head>
119
142
  <body>
120
143
  <header>
121
- <h1>🔔 <span id="page-title"></span></h1>
144
+ <h1 id="page-title"></h1>
122
145
  <button id="theme-toggle" type="button" aria-label="Toggle color theme"></button>
123
- <select id="langSelect" title="Language / 语言" style="margin-right:8px">
146
+ <select id="langSelect" title="Language / 语言">
124
147
  <option value="en">EN</option>
125
148
  <option value="zh">中文</option>
126
149
  </select>
127
- <nav>
128
- <a href="/" id="lnk-chat"></a>
129
- <a href="/tasks" id="lnk-tasks"></a>
130
- <a href="/memos" id="lnk-memos"></a>
131
- <a href="/settings" id="lnk-settings"></a>
132
- </nav>
150
+ <a href="/" id="lnk-chat"></a>
151
+ <a href="/tasks" id="lnk-tasks"></a>
152
+ <a href="/memos" id="lnk-memos"></a>
153
+ <a href="/settings" id="lnk-settings"></a>
133
154
  </header>
134
155
  <main>
135
156
  <div class="filters" id="filters">
@@ -208,19 +229,19 @@
208
229
  }
209
230
 
210
231
  async function load() {
211
- $list.innerHTML = '<div class="empty">加载中…</div>'
232
+ $list.innerHTML = `<div class="empty">${T.loading}</div>`
212
233
  try {
213
234
  const data = await window.imhub.api(`/api/reminders?status=${encodeURIComponent(currentStatus)}`)
214
235
  render(data.reminders || [])
215
236
  } catch (err) {
216
237
  $list.innerHTML = ''
217
- toast(`加载失败:${err.message}`, true)
238
+ toast(T.loadFailed.replace('{err}', err.message), true)
218
239
  }
219
240
  }
220
241
 
221
242
  function render(items) {
222
243
  if (!items.length) {
223
- $list.innerHTML = `<div class="empty">没有${statusLabel(currentStatus)}的提醒</div>`
244
+ $list.innerHTML = `<div class="empty">${T.emptyStatus.replace('{status}', statusLabel(currentStatus))}</div>`
224
245
  return
225
246
  }
226
247
  $list.innerHTML = items.map((r) => {
@@ -228,14 +249,14 @@
228
249
  const recurMeta = r.recurrence_label
229
250
  ? `<span class="recur">↻ ${escapeHtml(r.recurrence_label)}</span>` : ''
230
251
  const literalMeta = r.prompt_mode === 'literal'
231
- ? '<span class="literal">字面文本</span>' : ''
252
+ ? `<span class="literal">${T.literalText}</span>` : ''
232
253
  const platformMeta = r.platform
233
254
  ? `<span>${escapeHtml(r.platform)}</span>` : ''
234
255
  const showActions = currentStatus === 'pending'
235
256
  const actions = showActions
236
257
  ? `<div class="actions">
237
- <button class="btn" data-act="snooze" data-id="${r.id}">延 5 分钟</button>
238
- <button class="btn danger" data-act="cancel" data-id="${r.id}">取消</button>
258
+ <button class="btn" data-act="snooze" data-id="${r.id}">${T.snooze}</button>
259
+ <button class="btn danger" data-act="cancel" data-id="${r.id}">${T.cancel}</button>
239
260
  </div>`
240
261
  : ''
241
262
  return `<div class="reminder">
@@ -262,7 +283,10 @@
262
283
  }
263
284
 
264
285
  function statusLabel(s) {
265
- return ({ pending: '待发', fired: '已发', cancelled: '已取消', failed: '失败' })[s] || s
286
+ return ({
287
+ pending: T.statusPending, fired: T.statusFired,
288
+ cancelled: T.statusCancelled, failed: T.statusFailed,
289
+ })[s] || s
266
290
  }
267
291
 
268
292
  $filters.addEventListener('click', (e) => {
@@ -281,19 +305,19 @@
281
305
  btn.disabled = true
282
306
  try {
283
307
  if (act === 'cancel') {
284
- if (!confirm(`取消提醒 #${id}?(循环提醒会终止整条循环)`)) return
308
+ if (!confirm(T.confirmCancel.replace('{id}', id))) return
285
309
  await window.imhub.api(`/api/reminders/${id}/cancel`, { method: 'POST' })
286
- toast(`✅ 已取消 #${id}`)
310
+ toast(T.cancelled.replace('{id}', id))
287
311
  } else if (act === 'snooze') {
288
312
  await window.imhub.api(`/api/reminders/${id}/snooze`, {
289
313
  method: 'POST',
290
314
  body: JSON.stringify({ duration: '5m' }),
291
315
  })
292
- toast(`✅ #${id} 已延后 5 分钟`)
316
+ toast(T.snoozed.replace('{id}', id))
293
317
  }
294
318
  await load()
295
319
  } catch (err) {
296
- toast(`操作失败:${err.message}`, true)
320
+ toast(T.loadFailed.replace('{err}', err.message), true)
297
321
  } finally {
298
322
  btn.disabled = false
299
323
  }
@@ -25,8 +25,10 @@
25
25
  const T = {
26
26
  en: {
27
27
  title: 'Agim — Tasks',
28
- h1: 'Tasks & Schedules',
29
- backToChat: 'Chat',
28
+ h1: '🗂 Tasks & Schedules',
29
+ backToChat: 'Chat',
30
+ toReminders: 'Reminders',
31
+ toMemos: 'Memos',
30
32
  toSettings: 'Settings',
31
33
  tabsJobs: 'Jobs',
32
34
  tabsBackground: 'Background',
@@ -231,8 +233,10 @@
231
233
  },
232
234
  zh: {
233
235
  title: 'Agim — 任务',
234
- h1: '任务与定时',
235
- backToChat: '对话',
236
+ h1: '🗂 任务与定时',
237
+ backToChat: '对话',
238
+ toReminders: '提醒',
239
+ toMemos: '备忘',
236
240
  toSettings: '设置',
237
241
  tabsAudit: '审计',
238
242
  tabsJobs: '任务',
@@ -747,14 +751,14 @@
747
751
  <header>
748
752
  <h1 id="page-title"></h1>
749
753
  <button id="theme-toggle" type="button" aria-label="Toggle color theme"></button>
750
- <select id="langSelect" title="Language / 语言" style="margin-right:8px">
754
+ <select id="langSelect" title="Language / 语言">
751
755
  <option value="en">EN</option>
752
756
  <option value="zh">中文</option>
753
757
  </select>
754
- <a href="/">↩ <span id="lbl-chat"></span></a>
755
- <a href="/reminders" id="lnk-reminders">Reminders</a>
756
- <a href="/memos" id="lnk-memos">Memos</a>
757
- <a href="/settings" aria-label="Settings"><span id="lbl-settings"></span></a>
758
+ <a href="/" id="lnk-chat"></a>
759
+ <a href="/reminders" id="lnk-reminders"></a>
760
+ <a href="/memos" id="lnk-memos"></a>
761
+ <a href="/settings" id="lnk-settings"></a>
758
762
  </header>
759
763
  <main>
760
764
  <div class="tabs">
@@ -954,8 +958,10 @@
954
958
  // i18n string fills
955
959
  document.title = T.title;
956
960
  document.getElementById('page-title').textContent = T.h1;
957
- document.getElementById('lbl-chat').textContent = T.backToChat;
958
- document.getElementById('lbl-settings').textContent = T.toSettings;
961
+ document.getElementById('lnk-chat').textContent = T.backToChat;
962
+ document.getElementById('lnk-reminders').textContent = T.toReminders;
963
+ document.getElementById('lnk-memos').textContent = T.toMemos;
964
+ document.getElementById('lnk-settings').textContent = T.toSettings;
959
965
  document.getElementById('tab-jobs').textContent = T.tabsJobs;
960
966
  document.getElementById('tab-background').textContent = T.tabsBackground;
961
967
  document.getElementById('tab-subtasks').textContent = T.tabsSubtasks;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agim-cli",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Agim (阿吉姆) — universal messenger-to-agent bridge. Connect WeChat / Feishu / DingTalk / Telegram / Discord to Claude Code / Codex / Copilot / OpenCode, or any custom agent via ACP. Installs the `agim` command.",
5
5
  "type": "module",
6
6
  "bin": {