code wiki / _hdl_build / nx_vizsla_import.nx
nx_vizsla_import.nx source
↩ module page · 530 lines · 21143 B
1// nx_vizsla_import.nx -- NISHI VIZSLA: import your existing relationships into a tenant. Operator 2026-06-22:
2// "i want to import my linkedin my email my phone etc i may be forgetting things." These all export in OPEN
3// standard formats that we parse SOVEREIGNLY (no API, no cloud, your data never leaves the machine -- the whole
4// Vizsla differentiator):
5// vcard <base> <tid> <file.vcf> phone / email / iCloud / Google / Outlook contacts (RFC 6350 vCard)
6// linkedin <base> <tid> <conn.csv> LinkedIn "Connections.csv" (Settings->Data Privacy->Get a copy of your data)
7// Each parsed person -> a CONTACT line in the tenant's contacts.txt (default tier "network" -- you promote the
8// close ones); LinkedIn's "Connected On" date -> a TOUCH so imported pros aren't all flagged NEW. DEDUP vs the
9// tenant's EXISTING contact ids (read first) => re-importing is idempotent/safe. Name -> a stable handle
10// ([A-Za-z0-9-] lowercased). If the tenant has no contacts file yet, a default CONF+TIERs header is written.
11// Composes per-tenant like nx_vizsla_tenant; forks the blessed engine only to re-load LinkedIn touches.
12// license_tier: ORIGINAL expect_exit: 1 (no args)
13import "nx_syscalls.nx"
14const K_MAGIC_65536: i64 = 65536
15const K_MAGIC_1024: i64 = 1024
16
17func vi_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
18func vi_p(s: *u8) -> i64 { sys_write(1, s, vi_slen(s)); return 0 }
19
20func vi_eq(a: *u8, b: *u8) -> i64 {
21 var i: i64 = 0
22 var go: i64 = 1
23 while go == 1 {
24 if a[i] != b[i] { return 0 }
25 if a[i] == (0 as u8) { return 1 }
26 i = i + 1
27 }
28 return 0
29}
30
31func vi_cat(dst: *u8, off: i64, s: *u8) -> i64 {
32 var i: i64 = 0
33 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 }
34 return off + i
35}
36
37func vi_catn(dst: *u8, off: i64, v: i64) -> i64 {
38 var o: i64 = off
39 var m: i64 = v
40 if m < 0 { dst[o] = 45 as u8; o = o + 1; m = 0 - m }
41 let t: *u8 = sys_mmap(28)
42 var k: i64 = 0
43 if m == 0 { t[0] = 48 as u8; k = 1 }
44 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
45 var i: i64 = 0
46 while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 }
47 return o + k
48}
49
50func vi_dup(s: *u8) -> *u8 {
51 let n: i64 = vi_slen(s)
52 let d: *u8 = sys_mmap(n + 2)
53 var i: i64 = 0
54 while i <= n { d[i] = s[i]; i = i + 1 }
55 return d
56}
57
58func vi_atoi(s: *u8) -> i64 {
59 var v: i64 = 0
60 var i: i64 = 0
61 while s[i] != (0 as u8) { let d: i64 = (s[i] as i64) - 48; if d >= 0 { if d <= 9 { v = v * 10 + d } } i = i + 1 }
62 return v
63}
64
65// next whitespace-delimited word of s from offset off -> out; returns new offset
66func vi_word(s: *u8, off: i64, out: *u8, cap: i64) -> i64 {
67 var p: i64 = off
68 while s[p] == (32 as u8) { p = p + 1 }
69 var o: i64 = 0
70 while s[p] != (0 as u8) {
71 if s[p] == (32 as u8) { break }
72 if o < cap - 1 { out[o] = s[p]; o = o + 1 }
73 p = p + 1
74 }
75 out[o] = 0 as u8
76 return p
77}
78
79func vi_find(list: *i64, n: i64, s: *u8) -> i64 {
80 var i: i64 = 0
81 while i < n { if vi_eq(list[i] as *u8, s) == 1 { return i } i = i + 1 }
82 return 0 - 1
83}
84
85func vi_readall(path: *u8, szp: *i64) -> *u8 {
86 let fd: i64 = sys_openat_rd(path)
87 if fd < 0 { szp[0] = 0; return 0 as *u8 }
88 let sz: i64 = sys_lseek(fd, 0, 2)
89 sys_lseek(fd, 0, 0)
90 let buf: *u8 = sys_mmap(sz + 64)
91 var got: i64 = 0
92 var n: i64 = 1
93 while n > 0 { n = sys_read(fd, (buf as i64 + got) as *u8, K_MAGIC_65536); if n > 0 { got = got + n } }
94 sys_close(fd)
95 szp[0] = got
96 return buf
97}
98
99// upper bound on the number of lines in `path` (newlines + 1 for a possibly-unterminated final line); 0 if
100// unreadable. Used to size the dedup set DATA-DRIVEN so no fixed cap can silently drop ids.
101func vi_count_lines(path: *u8) -> i64 {
102 let szp: *i64 = sys_mmap(16) as *i64
103 let b: *u8 = vi_readall(path, szp)
104 let sz: i64 = szp[0]
105 if sz <= 0 { return 0 }
106 var nl: i64 = 0
107 var i: i64 = 0
108 while i < sz { if b[i] == (10 as u8) { nl = nl + 1 } i = i + 1 }
109 return nl + 1
110}
111
112func vi_exists(path: *u8) -> i64 { let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } sys_close(fd); return 1 }
113
114func vi_writefile(path: *u8, content: *u8) -> i64 {
115 let fd: i64 = sys_openat_wr(path, 0x1a4)
116 if fd < 0 { return 0 - 1 }
117 sys_write(fd, content, vi_slen(content))
118 sys_close(fd)
119 return 0
120}
121
122func vi_append(path: *u8, buf: *u8, n: i64) -> i64 {
123 let fd: i64 = sys_openat_append(path, 0x1a4)
124 if fd < 0 { return 0 - 1 }
125 sys_write(fd, buf, n)
126 sys_close(fd)
127 return 0
128}
129
130func vi_tpath(out: *u8, base: *u8, tid: *u8, suffix: *u8) -> i64 {
131 var o: i64 = 0
132 o = vi_cat(out, o, base); o = vi_cat(out, o, tid); o = vi_cat(out, o, suffix)
133 out[o] = 0 as u8
134 return o
135}
136
137// "John Smith" -> disp "John-Smith" (preserve case, separators->'-') + id "john-smith" (lowercased); ret len (0=empty)
138func vi_sanitize(src: *u8, disp: *u8, id: *u8) -> i64 {
139 var i: i64 = 0
140 var o: i64 = 0
141 var lastsep: i64 = 1
142 while src[i] != (0 as u8) {
143 let c: i64 = src[i] as i64
144 var isaln: i64 = 0
145 if c >= 48 { if c <= 57 { isaln = 1 } }
146 if c >= 65 { if c <= 90 { isaln = 1 } }
147 if c >= 97 { if c <= 122 { isaln = 1 } }
148 if isaln == 1 {
149 disp[o] = c as u8
150 var lc: i64 = c
151 if c >= 65 { if c <= 90 { lc = c + 32 } }
152 id[o] = lc as u8
153 o = o + 1
154 lastsep = 0
155 } else {
156 if lastsep == 0 { disp[o] = 45 as u8; id[o] = 45 as u8; o = o + 1; lastsep = 1 }
157 }
158 i = i + 1
159 }
160 if o > 0 { if disp[o - 1] == (45 as u8) { o = o - 1 } }
161 disp[o] = 0 as u8
162 id[o] = 0 as u8
163 return o
164}
165
166// 3-letter English month -> 1..12 (else 0)
167func vi_mon3(s: *u8) -> i64 {
168 if vi_eq(s, "Jan" as *u8) == 1 { return 1 }
169 if vi_eq(s, "Feb" as *u8) == 1 { return 2 }
170 if vi_eq(s, "Mar" as *u8) == 1 { return 3 }
171 if vi_eq(s, "Apr" as *u8) == 1 { return 4 }
172 if vi_eq(s, "May" as *u8) == 1 { return 5 }
173 if vi_eq(s, "Jun" as *u8) == 1 { return 6 }
174 if vi_eq(s, "Jul" as *u8) == 1 { return 7 }
175 if vi_eq(s, "Aug" as *u8) == 1 { return 8 }
176 if vi_eq(s, "Sep" as *u8) == 1 { return 9 }
177 if vi_eq(s, "Oct" as *u8) == 1 { return 10 }
178 if vi_eq(s, "Nov" as *u8) == 1 { return 11 }
179 if vi_eq(s, "Dec" as *u8) == 1 { return 12 }
180 return 0
181}
182
183// vCard BDAY value (e.g. "1985-07-03", "--0703", "19850703") -> "MM-DD" via last two digit-groups; ret 1 if ok
184func vi_bday_mmdd(src: *u8, out: *u8) -> i64 {
185 let g: *i64 = sys_mmap(8 * 8) as *i64
186 var ng: i64 = 0
187 var cur: i64 = 0
188 var indig: i64 = 0
189 var i: i64 = 0
190 var go: i64 = 1
191 while go == 1 {
192 let c: i64 = src[i] as i64
193 var isd: i64 = 0
194 if c >= 48 { if c <= 57 { isd = 1 } }
195 if isd == 1 { cur = cur * 10 + (c - 48); indig = 1 }
196 if isd == 0 { if indig == 1 { if ng < 8 { g[ng] = cur; ng = ng + 1 } cur = 0; indig = 0 } }
197 if c == 0 { go = 0 }
198 i = i + 1
199 }
200 if ng < 2 { out[0] = 45 as u8; out[1] = 0 as u8; return 0 }
201 let mm: i64 = g[ng - 2]
202 let dd: i64 = g[ng - 1]
203 if mm < 1 { out[0] = 45 as u8; out[1] = 0 as u8; return 0 }
204 if mm > 12 { out[0] = 45 as u8; out[1] = 0 as u8; return 0 }
205 if dd < 1 { out[0] = 45 as u8; out[1] = 0 as u8; return 0 }
206 if dd > 31 { out[0] = 45 as u8; out[1] = 0 as u8; return 0 }
207 var o: i64 = 0
208 if mm < 10 { out[o] = 48 as u8; o = o + 1 }
209 o = vi_catn(out, o, mm)
210 out[o] = 45 as u8; o = o + 1
211 if dd < 10 { out[o] = 48 as u8; o = o + 1 }
212 o = vi_catn(out, o, dd)
213 out[o] = 0 as u8
214 return 1
215}
216
217// line [0,llen) begins with "field" then ':' or ';' ?
218func vi_field_is(line: *u8, llen: i64, field: *u8) -> i64 {
219 let fl: i64 = vi_slen(field)
220 if fl >= llen { return 0 }
221 var j: i64 = 0
222 while j < fl { if line[j] != field[j] { return 0 } j = j + 1 }
223 let nx: i64 = line[fl] as i64
224 if nx == 58 { return 1 }
225 if nx == 59 { return 1 }
226 return 0
227}
228
229// value after the first ':' in line[0,llen)
230func vi_value(line: *u8, llen: i64, out: *u8, cap: i64) -> i64 {
231 var p: i64 = 0
232 while p < llen { if line[p] == (58 as u8) { break } p = p + 1 }
233 if p >= llen { out[0] = 0 as u8; return 0 }
234 p = p + 1
235 var o: i64 = 0
236 while p < llen { if o < cap - 1 { out[o] = line[p]; o = o + 1 } p = p + 1 }
237 out[o] = 0 as u8
238 return o
239}
240
241// k-th comma field of line[0,llen) -> out (k from 0)
242func vi_csv_field(line: *u8, llen: i64, k: i64, out: *u8, cap: i64) -> i64 {
243 var idx: i64 = 0
244 var p: i64 = 0
245 while idx < k {
246 if p >= llen { out[0] = 0 as u8; return 0 }
247 if line[p] == (44 as u8) { idx = idx + 1 }
248 p = p + 1
249 }
250 var o: i64 = 0
251 var go: i64 = 1
252 while go == 1 {
253 if p >= llen { go = 0 } else {
254 if line[p] == (44 as u8) { go = 0 } else {
255 if o < cap - 1 { out[o] = line[p]; o = o + 1 }
256 p = p + 1
257 }
258 }
259 }
260 out[o] = 0 as u8
261 return o
262}
263
264// fill seen[] with the tenant's existing CONTACT ids (so re-import is idempotent); returns count
265func vi_existing_ids(cpath: *u8, seen: *i64, ns0: i64, cap: i64) -> i64 {
266 let szp: *i64 = sys_mmap(16) as *i64
267 let b: *u8 = vi_readall(cpath, szp)
268 let sz: i64 = szp[0]
269 var ns: i64 = ns0
270 if sz <= 0 { return ns }
271 let tok: *u8 = sys_mmap(256)
272 var i: i64 = 0
273 while i < sz {
274 var e: i64 = i
275 var go: i64 = 1
276 while go == 1 { if e >= sz { go = 0 } else { if b[e] == (10 as u8) { go = 0 } else { e = e + 1 } } }
277 var le: i64 = e
278 if le > i { if b[le - 1] == (13 as u8) { le = le - 1 } }
279 let llen: i64 = le - i
280 let line: *u8 = (b as i64 + i) as *u8
281 if vi_field_is(line, llen, "CONTACT" as *u8) == 0 {
282 // not "CONTACT:" form -- CONTACT lines are space-delimited, so check prefix "CONTACT "
283 if llen > 8 {
284 if line[0] == (67 as u8) { if line[7] == (32 as u8) {
285 var p: i64 = 8
286 var o: i64 = 0
287 while p < llen { if line[p] == (32 as u8) { break } if o < 255 { tok[o] = line[p]; o = o + 1 } p = p + 1 }
288 tok[o] = 0 as u8
289 if o > 0 { if vi_find(seen, ns, tok) < 0 { if ns < cap { seen[ns] = vi_dup(tok) as i64; ns = ns + 1 } } }
290 } }
291 }
292 }
293 i = e + 1
294 }
295 return ns
296}
297
298func vi_ensure_contacts(cpath: *u8, lpath: *u8) -> i64 {
299 if vi_exists(cpath) == 0 { vi_writefile(cpath, "CONF soon_days 14\nTIER inner 14\nTIER close 45\nTIER network 120\n" as *u8) }
300 if vi_exists(lpath) == 0 { vi_writefile(lpath, "# interaction journal (imported + manual)\n" as *u8) }
301 return 0
302}
303
304func vi_emit_contact(cpath: *u8, id: *u8, disp: *u8, bday: *u8) -> i64 {
305 let buf: *u8 = sys_mmap(K_MAGIC_1024)
306 var o: i64 = 0
307 o = vi_cat(buf, o, "CONTACT " as *u8); o = vi_cat(buf, o, id); buf[o] = 32 as u8; o = o + 1
308 o = vi_cat(buf, o, disp); buf[o] = 32 as u8; o = o + 1
309 o = vi_cat(buf, o, "network " as *u8); o = vi_cat(buf, o, bday); buf[o] = 10 as u8; o = o + 1
310 return vi_append(cpath, buf, o)
311}
312
313func vi_emit_touch(lpath: *u8, date: *u8, id: *u8) -> i64 {
314 let buf: *u8 = sys_mmap(K_MAGIC_1024)
315 var o: i64 = 0
316 o = vi_cat(buf, o, "TOUCH " as *u8); o = vi_cat(buf, o, date); buf[o] = 32 as u8; o = o + 1
317 o = vi_cat(buf, o, id); o = vi_cat(buf, o, " connected-on-linkedin\n" as *u8)
318 return vi_append(lpath, buf, o)
319}
320
321// fork+exec the blessed relate engine "load <log> <prefix>" (re-ingest imported touches)
322func vi_reload(logp: *u8, prefix: *u8) -> i64 {
323 let pid: i64 = sys_fork()
324 if pid == 0 {
325 let argv: *i64 = sys_mmap(64) as *i64
326 argv[0] = "_offc/nx_vizsla_relate.elf" as *u8 as i64
327 argv[1] = "load" as *u8 as i64
328 argv[2] = logp as i64
329 argv[3] = prefix as i64
330 argv[4] = 0
331 let envp: *i64 = sys_mmap(16) as *i64
332 envp[0] = 0
333 sys_execve("_offc/nx_vizsla_relate.elf" as *u8, argv, envp)
334 sys_exit(127)
335 }
336 let st: *i64 = sys_mmap(16) as *i64
337 sys_wait4(pid, st, 0)
338 return (st[0] >> 8) & 0xff
339}
340
341// ---------------- VCARD ----------------
342func vi_cmd_vcard(argc: i64, argv: *i64) -> i64 {
343 if argc < 5 { vi_p("VIZSLA-IMPORT vcard needs <base> <tid> <file.vcf> -- fail loud\n" as *u8); return 1 }
344 let base: *u8 = argv[2] as *u8
345 let tid: *u8 = argv[3] as *u8
346 let vpath: *u8 = argv[4] as *u8
347 let cpath: *u8 = sys_mmap(K_MAGIC_1024); vi_tpath(cpath, base, tid, ".contacts.txt" as *u8)
348 let lpath: *u8 = sys_mmap(K_MAGIC_1024); vi_tpath(lpath, base, tid, ".relate_log.txt" as *u8)
349 vi_ensure_contacts(cpath, lpath)
350
351 // DATA-DRIVEN dedup-set size: seen[] holds at most one id per line across the tenant's existing contacts
352 // + the import source, so size it from their actual line counts. No fixed cap -> re-import stays idempotent
353 // at any scale (this was the janitor-caps silent-truncation smell: a 4096 cap dropped ids past 4096 and
354 // re-emitted them as duplicates every re-import). Eaten here, data-driven.
355 let cap: i64 = vi_count_lines(cpath) + vi_count_lines(vpath)
356 let seen: *i64 = sys_mmap(8 * cap) as *i64
357 var ns: i64 = vi_existing_ids(cpath, seen, 0, cap)
358
359 let szp: *i64 = sys_mmap(16) as *i64
360 let b: *u8 = vi_readall(vpath, szp)
361 let sz: i64 = szp[0]
362 if sz <= 0 { vi_p("VIZSLA-IMPORT vcf MISSING/EMPTY -- fail loud\n" as *u8); return 1 }
363
364 let tmp: *u8 = sys_mmap(512)
365 let disp: *u8 = sys_mmap(256)
366 let id: *u8 = sys_mmap(256)
367 let bday: *u8 = sys_mmap(32)
368 var have_fn: i64 = 0
369 var have_bd: i64 = 0
370 var imported: i64 = 0
371 var skipped: i64 = 0
372 var i: i64 = 0
373 while i < sz {
374 var e: i64 = i
375 var go: i64 = 1
376 while go == 1 { if e >= sz { go = 0 } else { if b[e] == (10 as u8) { go = 0 } else { e = e + 1 } } }
377 var le: i64 = e
378 if le > i { if b[le - 1] == (13 as u8) { le = le - 1 } }
379 let llen: i64 = le - i
380 let line: *u8 = (b as i64 + i) as *u8
381 if vi_field_is(line, llen, "BEGIN" as *u8) == 1 { have_fn = 0; have_bd = 0 }
382 if vi_field_is(line, llen, "FN" as *u8) == 1 {
383 vi_value(line, llen, tmp, 512)
384 if vi_sanitize(tmp, disp, id) > 0 { have_fn = 1 }
385 }
386 if vi_field_is(line, llen, "BDAY" as *u8) == 1 {
387 vi_value(line, llen, tmp, 512)
388 have_bd = vi_bday_mmdd(tmp, bday)
389 }
390 if vi_field_is(line, llen, "END" as *u8) == 1 {
391 if have_fn == 1 {
392 if vi_find(seen, ns, id) < 0 {
393 var bd: *u8 = "-" as *u8
394 if have_bd == 1 { bd = bday }
395 vi_emit_contact(cpath, id, disp, bd)
396 if ns < cap { seen[ns] = vi_dup(id) as i64; ns = ns + 1 }
397 imported = imported + 1
398 } else { skipped = skipped + 1 }
399 }
400 have_fn = 0; have_bd = 0
401 }
402 i = e + 1
403 }
404
405 let rep: *u8 = sys_mmap(256)
406 var o: i64 = 0
407 o = vi_cat(rep, o, "VIZSLA-IMPORT-VCARD tenant=" as *u8); o = vi_cat(rep, o, tid)
408 o = vi_cat(rep, o, " imported=" as *u8); o = vi_catn(rep, o, imported)
409 o = vi_cat(rep, o, " skipped=" as *u8); o = vi_catn(rep, o, skipped)
410 o = vi_cat(rep, o, " (review " as *u8); o = vi_cat(rep, o, cpath); o = vi_cat(rep, o, ", promote close ones from network)\n" as *u8)
411 sys_write(1, rep, o)
412 return 0
413}
414
415// ---------------- LINKEDIN ----------------
416func vi_cmd_linkedin(argc: i64, argv: *i64) -> i64 {
417 if argc < 5 { vi_p("VIZSLA-IMPORT linkedin needs <base> <tid> <Connections.csv> -- fail loud\n" as *u8); return 1 }
418 let base: *u8 = argv[2] as *u8
419 let tid: *u8 = argv[3] as *u8
420 let cpath0: *u8 = argv[4] as *u8
421 let cpath: *u8 = sys_mmap(K_MAGIC_1024); vi_tpath(cpath, base, tid, ".contacts.txt" as *u8)
422 let lpath: *u8 = sys_mmap(K_MAGIC_1024); vi_tpath(lpath, base, tid, ".relate_log.txt" as *u8)
423 let prefix: *u8 = sys_mmap(K_MAGIC_1024); vi_tpath(prefix, base, tid, ".relate-" as *u8)
424 vi_ensure_contacts(cpath, lpath)
425
426 // DATA-DRIVEN dedup-set size (see vi_cmd_vcard): from existing-contacts + source line counts, no fixed cap.
427 let cap: i64 = vi_count_lines(cpath) + vi_count_lines(cpath0)
428 let seen: *i64 = sys_mmap(8 * cap) as *i64
429 var ns: i64 = vi_existing_ids(cpath, seen, 0, cap)
430
431 let szp: *i64 = sys_mmap(16) as *i64
432 let b: *u8 = vi_readall(cpath0, szp)
433 let sz: i64 = szp[0]
434 if sz <= 0 { vi_p("VIZSLA-IMPORT csv MISSING/EMPTY -- fail loud\n" as *u8); return 1 }
435
436 // locate header columns by name (robust to column order)
437 var i_first: i64 = 0 - 1
438 var i_last: i64 = 0 - 1
439 var i_conn: i64 = 0 - 1
440 let fld: *u8 = sys_mmap(256)
441 let first: *u8 = sys_mmap(256)
442 let last: *u8 = sys_mmap(256)
443 let conn: *u8 = sys_mmap(256)
444 let nm: *u8 = sys_mmap(512)
445 let disp: *u8 = sys_mmap(256)
446 let id: *u8 = sys_mmap(256)
447 let mon: *u8 = sys_mmap(16)
448 let dnum: *u8 = sys_mmap(16)
449 let ynum: *u8 = sys_mmap(16)
450 let date: *u8 = sys_mmap(32)
451 var imported: i64 = 0
452 var skipped: i64 = 0
453 var i: i64 = 0
454 while i < sz {
455 var e: i64 = i
456 var go: i64 = 1
457 while go == 1 { if e >= sz { go = 0 } else { if b[e] == (10 as u8) { go = 0 } else { e = e + 1 } } }
458 var le: i64 = e
459 if le > i { if b[le - 1] == (13 as u8) { le = le - 1 } }
460 let llen: i64 = le - i
461 let line: *u8 = (b as i64 + i) as *u8
462 if i_first < 0 {
463 // still seeking the header row (the one whose first field is "First Name")
464 vi_csv_field(line, llen, 0, fld, 256)
465 if vi_eq(fld, "First Name" as *u8) == 1 {
466 var k: i64 = 0
467 while k < 16 {
468 let got: i64 = vi_csv_field(line, llen, k, fld, 256)
469 if vi_eq(fld, "First Name" as *u8) == 1 { i_first = k }
470 if vi_eq(fld, "Last Name" as *u8) == 1 { i_last = k }
471 if vi_eq(fld, "Connected On" as *u8) == 1 { i_conn = k }
472 if got == 0 { k = 16 } else { k = k + 1 }
473 }
474 }
475 } else {
476 vi_csv_field(line, llen, i_first, first, 256)
477 vi_csv_field(line, llen, i_last, last, 256)
478 var no: i64 = 0
479 no = vi_cat(nm, no, first); nm[no] = 32 as u8; no = no + 1; no = vi_cat(nm, no, last); nm[no] = 0 as u8
480 if vi_sanitize(nm, disp, id) > 0 {
481 if vi_find(seen, ns, id) < 0 {
482 vi_emit_contact(cpath, id, disp, "-" as *u8)
483 if ns < cap { seen[ns] = vi_dup(id) as i64; ns = ns + 1 }
484 imported = imported + 1
485 // "Connected On" e.g. "15 Jun 2026" -> TOUCH 2026-06-15
486 if i_conn >= 0 {
487 vi_csv_field(line, llen, i_conn, conn, 256)
488 var ci: i64 = 0
489 ci = vi_word(conn, ci, dnum, 16)
490 ci = vi_word(conn, ci, mon, 16)
491 ci = vi_word(conn, ci, ynum, 16)
492 let mn: i64 = vi_mon3(mon)
493 if mn > 0 {
494 var do2: i64 = 0
495 do2 = vi_cat(date, do2, ynum); date[do2] = 45 as u8; do2 = do2 + 1
496 if mn < 10 { date[do2] = 48 as u8; do2 = do2 + 1 }
497 do2 = vi_catn(date, do2, mn); date[do2] = 45 as u8; do2 = do2 + 1
498 let dv: i64 = vi_atoi(dnum)
499 if dv < 10 { date[do2] = 48 as u8; do2 = do2 + 1 }
500 do2 = vi_catn(date, do2, dv); date[do2] = 0 as u8
501 vi_emit_touch(lpath, date, id)
502 }
503 }
504 } else { skipped = skipped + 1 }
505 }
506 }
507 i = e + 1
508 }
509 if i_first < 0 { vi_p("VIZSLA-IMPORT linkedin: no 'First Name' header found -- is this a LinkedIn Connections.csv? -- fail loud\n" as *u8); return 1 }
510
511 vi_reload(lpath, prefix)
512
513 let rep: *u8 = sys_mmap(256)
514 var o: i64 = 0
515 o = vi_cat(rep, o, "VIZSLA-IMPORT-LINKEDIN tenant=" as *u8); o = vi_cat(rep, o, tid)
516 o = vi_cat(rep, o, " imported=" as *u8); o = vi_catn(rep, o, imported)
517 o = vi_cat(rep, o, " skipped=" as *u8); o = vi_catn(rep, o, skipped)
518 o = vi_cat(rep, o, " (connection dates -> TOUCHes; promote close ones from network)\n" as *u8)
519 sys_write(1, rep, o)
520 return 0
521}
522
523func main(argc: i64, argv: *i64) -> i64 {
524 if argc < 2 { vi_p("VIZSLA-IMPORT usage: <vcard|linkedin> <base> <tenant-id> <file> -- fail loud\n" as *u8); return 1 }
525 let cmd: *u8 = argv[1] as *u8
526 if vi_eq(cmd, "vcard" as *u8) == 1 { return vi_cmd_vcard(argc, argv) }
527 if vi_eq(cmd, "linkedin" as *u8) == 1 { return vi_cmd_linkedin(argc, argv) }
528 vi_p("VIZSLA-IMPORT unknown command (vcard|linkedin) -- fail loud\n" as *u8)
529 return 1
530}